committed by
GitHub
129 changed files with 6621 additions and 1957 deletions
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,93 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.notification.channels; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.thingsboard.server.dao.util.ImageUtils; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @link <a href="https://adaptivecards.io/designer/">AdaptiveCard Designer</a> |
||||
|
*/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public class TeamsAdaptiveCard { |
||||
|
private String type = "message"; |
||||
|
private List<Attachment> attachments; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public static class Attachment { |
||||
|
private String contentType = "application/vnd.microsoft.card.adaptive"; |
||||
|
private AdaptiveCard content; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public static class AdaptiveCard { |
||||
|
@JsonProperty("$schema") |
||||
|
private final String schema = "http://adaptivecards.io/schemas/adaptive-card.json"; |
||||
|
private final String type = "AdaptiveCard"; |
||||
|
private BackgroundImage backgroundImage; |
||||
|
@JsonProperty("body") |
||||
|
private List<TextBlock> textBlocks = new ArrayList<>(); |
||||
|
private List<ActionOpenUrl> actions = new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public static class BackgroundImage { |
||||
|
private String url; |
||||
|
private final String fillMode = "repeat"; |
||||
|
|
||||
|
public BackgroundImage(String color) { |
||||
|
// This is the only one way how to specify color the custom color for the card
|
||||
|
url = ImageUtils.getEmbeddedBase64EncodedImg(color); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public static class TextBlock { |
||||
|
private final String type = "TextBlock"; |
||||
|
private String text; |
||||
|
private String weight = "Normal"; |
||||
|
private String size = "Medium"; |
||||
|
private String spacing = "None"; |
||||
|
private String color = "#FFFFFF"; |
||||
|
private final boolean wrap = true; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public static class ActionOpenUrl { |
||||
|
private final String type = "Action.OpenUrl"; |
||||
|
private String title; |
||||
|
private String url; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.notification.channels; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonInclude; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class TeamsMessageCard { |
||||
|
@JsonProperty("@type") |
||||
|
private final String type = "MessageCard"; |
||||
|
@JsonProperty("@context") |
||||
|
private final String context = "http://schema.org/extensions"; |
||||
|
private String themeColor; |
||||
|
private String summary; |
||||
|
private String text; |
||||
|
private List<Section> sections; |
||||
|
private List<ActionCard> potentialAction; |
||||
|
|
||||
|
@Data |
||||
|
public static class Section { |
||||
|
private String activityTitle; |
||||
|
private String activitySubtitle; |
||||
|
private String activityImage; |
||||
|
private List<Fact> facts; |
||||
|
private boolean markdown; |
||||
|
|
||||
|
@Data |
||||
|
public static class Fact { |
||||
|
private final String name; |
||||
|
private final String value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@JsonInclude(JsonInclude.Include.NON_NULL) |
||||
|
public static class ActionCard { |
||||
|
@JsonProperty("@type") |
||||
|
private String type; // ActionCard, OpenUri
|
||||
|
private String name; |
||||
|
private List<Input> inputs; // for ActionCard
|
||||
|
private List<Action> actions; // for ActionCard
|
||||
|
private List<Target> targets; |
||||
|
|
||||
|
@Data |
||||
|
public static class Input { |
||||
|
@JsonProperty("@type") |
||||
|
private String type; // TextInput, DateInput, MultichoiceInput
|
||||
|
private String id; |
||||
|
private boolean isMultiple; |
||||
|
private String title; |
||||
|
private boolean isMultiSelect; |
||||
|
|
||||
|
@Data |
||||
|
public static class Choice { |
||||
|
private final String display; |
||||
|
private final String value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class Action { |
||||
|
@JsonProperty("@type") |
||||
|
private final String type; // HttpPOST
|
||||
|
private final String name; |
||||
|
private final String target; // url
|
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class Target { |
||||
|
private final String os; |
||||
|
private final String uri; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,343 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.delay; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
import org.junit.jupiter.params.ParameterizedTest; |
||||
|
import org.junit.jupiter.params.provider.Arguments; |
||||
|
import org.junit.jupiter.params.provider.EnumSource; |
||||
|
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
import org.junit.jupiter.params.provider.ValueSource; |
||||
|
import org.mockito.ArgumentCaptor; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
import org.springframework.test.util.ReflectionTestUtils; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; |
||||
|
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.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.msg.TbMsgType; |
||||
|
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; |
||||
|
import org.thingsboard.server.common.data.rule.RuleNode; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
||||
|
|
||||
|
import java.util.EnumSet; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.stream.Collectors; |
||||
|
import java.util.stream.Stream; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.assertj.core.api.Assertions.assertThatNoException; |
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.BDDMockito.given; |
||||
|
import static org.mockito.BDDMockito.spy; |
||||
|
import static org.mockito.BDDMockito.then; |
||||
|
import static org.mockito.BDDMockito.willAnswer; |
||||
|
|
||||
|
@ExtendWith(MockitoExtension.class) |
||||
|
public class TbMsgDelayNodeTest extends AbstractRuleNodeUpgradeTest { |
||||
|
|
||||
|
private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("20107cf0-1c5e-4ac4-8131-7c466c955a7c")); |
||||
|
private final RuleNodeId RULE_NODE_ID = new RuleNodeId(UUID.fromString("1be24225-b669-4b26-ab7e-083aaa82d0a0")); |
||||
|
|
||||
|
private final Set<TimeUnit> supportedTimeUnits = EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS); |
||||
|
private final String supportedTimeUnitsStr = supportedTimeUnits.stream().map(TimeUnit::name).collect(Collectors.joining(", ")); |
||||
|
|
||||
|
private TbMsgDelayNode node; |
||||
|
private TbMsgDelayNodeConfiguration config; |
||||
|
|
||||
|
@Mock |
||||
|
private TbContext ctxMock; |
||||
|
@Mock |
||||
|
private RuleNode ruleNodeMock; |
||||
|
|
||||
|
@BeforeEach |
||||
|
public void setUp() { |
||||
|
node = spy(new TbMsgDelayNode()); |
||||
|
config = new TbMsgDelayNodeConfiguration().defaultConfiguration(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void verifyDefaultConfig() { |
||||
|
assertThat(config.getPeriod()).isEqualTo("60"); |
||||
|
assertThat(config.getMaxPendingMsgs()).isEqualTo(1000); |
||||
|
assertThat(config.getTimeUnit()).isEqualTo(TimeUnit.SECONDS.name()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenDefaultConfig_whenInit_thenOk() { |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
assertThatNoException().isThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))); |
||||
|
} |
||||
|
|
||||
|
@ParameterizedTest |
||||
|
@ValueSource(ints = {-1, 0, 5000000}) |
||||
|
public void givenInvalidMaxPendingMsgsValue_whenInit_thenThrowsException(int maxPendingMsgs) { |
||||
|
config.setMaxPendingMsgs(maxPendingMsgs); |
||||
|
verifyValidationExceptionOnInit(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenPeriodIsNull_whenInit_thenThrowsException() { |
||||
|
config.setPeriod(null); |
||||
|
verifyValidationExceptionOnInit(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenTimeUnitIsNull_whenInit_thenThrowsException() { |
||||
|
config.setTimeUnit(null); |
||||
|
verifyValidationExceptionOnInit(); |
||||
|
} |
||||
|
|
||||
|
@ParameterizedTest |
||||
|
@MethodSource |
||||
|
public void givenPeriodValueAndPeriodTimeUnitPatterns_whenOnMsg_thenTellSelfTickMsgAndEnqueueForTellNext( |
||||
|
String periodPattern, String timeUnitPattern, TbMsgMetaData metaData, String data, long expectedDelay) throws TbNodeException { |
||||
|
config.setPeriod(periodPattern); |
||||
|
config.setTimeUnit(timeUnitPattern); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
|
||||
|
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); |
||||
|
|
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, data); |
||||
|
var tickMsg = TbMsg.newMsg(TbMsgType.DELAY_TIMEOUT_SELF_MSG, RULE_NODE_ID, TbMsgMetaData.EMPTY, msg.getId().toString()); |
||||
|
|
||||
|
given(ctxMock.newMsg(any(), any(TbMsgType.class), any(), any(), any(), any())).willReturn(tickMsg); |
||||
|
given(ctxMock.getSelfId()).willReturn(RULE_NODE_ID); |
||||
|
willAnswer(invocation -> { |
||||
|
node.onMsg(ctxMock, invocation.getArgument(0)); |
||||
|
return null; |
||||
|
}).given(ctxMock).tellSelf(any(TbMsg.class), any(Long.class)); |
||||
|
|
||||
|
node.onMsg(ctxMock, msg); |
||||
|
|
||||
|
then(ctxMock).should().tellSelf(tickMsg, expectedDelay); |
||||
|
then(ctxMock).should().ack(msg); |
||||
|
ArgumentCaptor<TbMsg> actualMsg = ArgumentCaptor.forClass(TbMsg.class); |
||||
|
then(ctxMock).should().enqueueForTellNext(actualMsg.capture(), eq(TbNodeConnectionType.SUCCESS)); |
||||
|
assertThat(actualMsg.getValue()).usingRecursiveComparison().ignoringFields("id", "ts").isEqualTo(msg); |
||||
|
} |
||||
|
|
||||
|
private static Stream<Arguments> givenPeriodValueAndPeriodTimeUnitPatterns_whenOnMsg_thenTellSelfTickMsgAndEnqueueForTellNext() { |
||||
|
return Stream.of( |
||||
|
Arguments.of("1", "HOURS", TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, TimeUnit.HOURS.toMillis(1L)), |
||||
|
Arguments.of("${md-period}", "${md-time-unit}", |
||||
|
new TbMsgMetaData(Map.of( |
||||
|
"md-period", "5", |
||||
|
"md-time-unit", "MINUTES" |
||||
|
)), TbMsg.EMPTY_JSON_OBJECT, TimeUnit.MINUTES.toMillis(5L)), |
||||
|
Arguments.of("$[msg-period]", "$[msg-time-unit]", TbMsgMetaData.EMPTY, |
||||
|
"{\"msg-period\":10,\"msg-time-unit\":\"SECONDS\"}", TimeUnit.SECONDS.toMillis(10L)) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@ParameterizedTest |
||||
|
@EnumSource(TimeUnit.class) |
||||
|
public void givenTimeUnit_whenOnMsg_thenVerify(TimeUnit timeUnit) throws TbNodeException { |
||||
|
config.setTimeUnit(timeUnit.name()); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
|
||||
|
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); |
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); |
||||
|
if (supportedTimeUnits.contains(timeUnit)) { |
||||
|
assertThatNoException().isThrownBy(() -> node.onMsg(ctxMock, msg)); |
||||
|
} else { |
||||
|
assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) |
||||
|
.isInstanceOf(RuntimeException.class) |
||||
|
.hasMessage("Time unit '" + timeUnit + "' is not supported! Only " + supportedTimeUnitsStr + " are supported."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenPeriodIsUnparsable_whenOnMsg_thenThrowsException() throws TbNodeException { |
||||
|
config.setPeriod("five"); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
|
||||
|
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); |
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); |
||||
|
assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) |
||||
|
.isInstanceOf(NumberFormatException.class) |
||||
|
.hasMessage("Can't parse period value : five"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenInvalidTimeUnit_whenOnMsg_thenThrowsException() throws TbNodeException { |
||||
|
config.setTimeUnit("sec"); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
|
||||
|
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); |
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); |
||||
|
assertThatThrownBy(() -> node.onMsg(ctxMock, msg)) |
||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||
|
.hasMessage("Invalid value for period time unit : sec"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenMaxLimitOfPendingMsgsReached_whenOnMsg_thenTellFailure() throws TbNodeException { |
||||
|
config.setMaxPendingMsgs(1); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNodeMock); |
||||
|
|
||||
|
node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); |
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); |
||||
|
for (int i = 0; i < 2; i++) { |
||||
|
node.onMsg(ctxMock, msg); |
||||
|
} |
||||
|
|
||||
|
ArgumentCaptor<Throwable> throwable = ArgumentCaptor.forClass(Throwable.class); |
||||
|
then(ctxMock).should().tellFailure(eq(msg), throwable.capture()); |
||||
|
assertThat(throwable.getValue()).isInstanceOf(RuntimeException.class).hasMessage("Max limit of pending messages reached!"); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void verifyDestroyMethod() { |
||||
|
var msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); |
||||
|
var pendingMsgs = new ConcurrentHashMap<>(); |
||||
|
pendingMsgs.put(UUID.fromString("321f0301-9bed-4e7d-b92f-a978f53ec5d6"), msg); |
||||
|
ReflectionTestUtils.setField(node, "pendingMsgs", pendingMsgs); |
||||
|
var actualPendingMsgs = (Map<UUID, TbMsg>) ReflectionTestUtils.getField(node, "pendingMsgs"); |
||||
|
assertThat(actualPendingMsgs).isEqualTo(pendingMsgs); |
||||
|
|
||||
|
node.destroy(); |
||||
|
|
||||
|
assertThat(actualPendingMsgs).isEmpty(); |
||||
|
} |
||||
|
|
||||
|
private void verifyValidationExceptionOnInit() { |
||||
|
RuleNode ruleNode = new RuleNode(); |
||||
|
ruleNode.setName("test"); |
||||
|
given(ctxMock.getSelf()).willReturn(ruleNode); |
||||
|
String errorPrefix = "'test' node configuration is invalid: "; |
||||
|
assertThatThrownBy(() -> node.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(config)))) |
||||
|
.isInstanceOf(TbNodeException.class) |
||||
|
.hasMessageContaining(errorPrefix) |
||||
|
.extracting(e -> ((TbNodeException) e).isUnrecoverable()) |
||||
|
.isEqualTo(true); |
||||
|
} |
||||
|
|
||||
|
private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { |
||||
|
return Stream.of( |
||||
|
// config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns does not exist and periodInSeconds exists)
|
||||
|
Arguments.of(0, |
||||
|
""" |
||||
|
{ |
||||
|
"periodInSeconds": 13, |
||||
|
"maxPendingMsgs": 1000, |
||||
|
"periodInSecondsPattern": "17" |
||||
|
} |
||||
|
""", |
||||
|
true, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "13", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""" |
||||
|
), |
||||
|
// config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns and periodInSeconds do not exist)
|
||||
|
Arguments.of(0, |
||||
|
""" |
||||
|
{ |
||||
|
"maxPendingMsgs": 1000, |
||||
|
"periodInSecondsPattern": "17" |
||||
|
} |
||||
|
""", |
||||
|
true, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "60", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""" |
||||
|
), |
||||
|
// config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPatterns is false)
|
||||
|
Arguments.of(0, |
||||
|
""" |
||||
|
{ |
||||
|
"periodInSeconds": 60, |
||||
|
"maxPendingMsgs": 1000, |
||||
|
"periodInSecondsPattern": null, |
||||
|
"useMetadataPeriodInSecondsPatterns": false |
||||
|
} |
||||
|
""", |
||||
|
true, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "60", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""" |
||||
|
), |
||||
|
// config for version 1 with upgrade from version 0 (useMetadataPeriodInSecondsPattern is true)
|
||||
|
Arguments.of(0, |
||||
|
""" |
||||
|
{ |
||||
|
"periodInSeconds": 60, |
||||
|
"maxPendingMsgs": 1000, |
||||
|
"periodInSecondsPattern": "${period-pattern}", |
||||
|
"useMetadataPeriodInSecondsPatterns": true |
||||
|
} |
||||
|
""", |
||||
|
true, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "${period-pattern}", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""" |
||||
|
), |
||||
|
// config for version 1 with upgrade from version 0 (hasChanges is false)
|
||||
|
Arguments.of(0, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "${period-pattern}", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""", |
||||
|
false, |
||||
|
""" |
||||
|
{ |
||||
|
"period": "${period-pattern}", |
||||
|
"timeUnit": "SECONDS", |
||||
|
"maxPendingMsgs": 1000 |
||||
|
} |
||||
|
""" |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected TbNode getTestNode() { |
||||
|
return node; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Directive, inject, Input, OnDestroy, TemplateRef } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
|
||||
|
@Directive() |
||||
|
export abstract class GatewayConnectorBasicConfigDirective<InputBasicConfig, OutputBasicConfig> |
||||
|
implements ControlValueAccessor, Validator, OnDestroy { |
||||
|
|
||||
|
@Input() generalTabContent: TemplateRef<any>; |
||||
|
|
||||
|
basicFormGroup: FormGroup; |
||||
|
|
||||
|
protected fb = inject(FormBuilder); |
||||
|
protected onChange!: (value: OutputBasicConfig) => void; |
||||
|
protected onTouched!: () => void; |
||||
|
protected destroy$ = new Subject<void>(); |
||||
|
|
||||
|
constructor() { |
||||
|
this.basicFormGroup = this.initBasicFormGroup(); |
||||
|
|
||||
|
this.basicFormGroup.valueChanges |
||||
|
.pipe(takeUntil(this.destroy$)) |
||||
|
.subscribe((value) => this.onBasicFormGroupChange(value)); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.basicFormGroup.valid ? null : { basicFormGroup: { valid: false } }; |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: (value: OutputBasicConfig) => void): void { |
||||
|
this.onChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: () => void): void { |
||||
|
this.onTouched = fn; |
||||
|
} |
||||
|
|
||||
|
writeValue(config: OutputBasicConfig): void { |
||||
|
this.basicFormGroup.setValue(this.mapConfigToFormValue(config), { emitEvent: false }); |
||||
|
} |
||||
|
|
||||
|
protected onBasicFormGroupChange(value: InputBasicConfig): void { |
||||
|
this.onChange(this.getMappedValue(value)); |
||||
|
this.onTouched(); |
||||
|
} |
||||
|
|
||||
|
protected abstract mapConfigToFormValue(config: OutputBasicConfig): InputBasicConfig; |
||||
|
protected abstract getMappedValue(config: InputBasicConfig): OutputBasicConfig; |
||||
|
protected abstract initBasicFormGroup(): FormGroup; |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { isNumber, isString } from '@core/utils'; |
||||
|
|
||||
|
export abstract class GatewayConnectorVersionProcessor<BasicConfig> { |
||||
|
gatewayVersion: number; |
||||
|
configVersion: number; |
||||
|
|
||||
|
protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector<BasicConfig>) { |
||||
|
this.gatewayVersion = this.parseVersion(this.gatewayVersionIn); |
||||
|
this.configVersion = this.parseVersion(connector.configVersion); |
||||
|
} |
||||
|
|
||||
|
getProcessedByVersion(): GatewayConnector<BasicConfig> { |
||||
|
if (this.isVersionUpdateNeeded()) { |
||||
|
return this.isVersionUpgradeNeeded() |
||||
|
? this.getUpgradedVersion() |
||||
|
: this.getDowngradedVersion(); |
||||
|
} |
||||
|
|
||||
|
return this.connector; |
||||
|
} |
||||
|
|
||||
|
private isVersionUpdateNeeded(): boolean { |
||||
|
if (!this.gatewayVersion) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return this.configVersion !== this.gatewayVersion; |
||||
|
} |
||||
|
|
||||
|
private isVersionUpgradeNeeded(): boolean { |
||||
|
return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion); |
||||
|
} |
||||
|
|
||||
|
private parseVersion(version: string | number): number { |
||||
|
if (isNumber(version)) { |
||||
|
return version as number; |
||||
|
} |
||||
|
|
||||
|
return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0; |
||||
|
} |
||||
|
|
||||
|
protected abstract getDowngradedVersion(): GatewayConnector<BasicConfig>; |
||||
|
protected abstract getUpgradedVersion(): GatewayConnector<BasicConfig>; |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
GatewayConnector, |
||||
|
ModbusBasicConfig, |
||||
|
ModbusBasicConfig_v3_5_2, |
||||
|
ModbusLegacyBasicConfig, |
||||
|
ModbusLegacySlave, |
||||
|
ModbusSlave, |
||||
|
} from '../gateway-widget.models'; |
||||
|
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; |
||||
|
import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; |
||||
|
|
||||
|
export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor<any> { |
||||
|
|
||||
|
constructor( |
||||
|
protected gatewayVersionIn: string, |
||||
|
protected connector: GatewayConnector<ModbusBasicConfig> |
||||
|
) { |
||||
|
super(gatewayVersionIn, connector); |
||||
|
} |
||||
|
|
||||
|
getUpgradedVersion(): GatewayConnector<ModbusBasicConfig_v3_5_2> { |
||||
|
const configurationJson = this.connector.configurationJson; |
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson: { |
||||
|
master: configurationJson.master?.slaves |
||||
|
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master) |
||||
|
: { slaves: [] }, |
||||
|
slave: configurationJson.slave |
||||
|
? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) |
||||
|
: {} as ModbusSlave, |
||||
|
}, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<ModbusBasicConfig_v3_5_2>; |
||||
|
} |
||||
|
|
||||
|
getDowngradedVersion(): GatewayConnector<ModbusLegacyBasicConfig> { |
||||
|
const configurationJson = this.connector.configurationJson; |
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson: { |
||||
|
...configurationJson, |
||||
|
slave: configurationJson.slave |
||||
|
? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(configurationJson.slave as ModbusSlave) |
||||
|
: {} as ModbusLegacySlave, |
||||
|
master: configurationJson.master, |
||||
|
}, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<ModbusLegacyBasicConfig>; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { isEqual } from '@core/utils'; |
||||
|
import { |
||||
|
GatewayConnector, |
||||
|
MQTTBasicConfig, |
||||
|
MQTTBasicConfig_v3_5_2, |
||||
|
MQTTLegacyBasicConfig, |
||||
|
RequestMappingData, |
||||
|
RequestType, |
||||
|
} from '../gateway-widget.models'; |
||||
|
import { MqttVersionMappingUtil } from '../utils/mqtt-version-mapping.util'; |
||||
|
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; |
||||
|
|
||||
|
export class MqttVersionProcessor extends GatewayConnectorVersionProcessor<MQTTBasicConfig> { |
||||
|
|
||||
|
private readonly mqttRequestTypeKeys = Object.values(RequestType); |
||||
|
|
||||
|
constructor( |
||||
|
protected gatewayVersionIn: string, |
||||
|
protected connector: GatewayConnector<MQTTBasicConfig> |
||||
|
) { |
||||
|
super(gatewayVersionIn, connector); |
||||
|
} |
||||
|
|
||||
|
getUpgradedVersion(): GatewayConnector<MQTTBasicConfig_v3_5_2> { |
||||
|
const { |
||||
|
connectRequests, |
||||
|
disconnectRequests, |
||||
|
attributeRequests, |
||||
|
attributeUpdates, |
||||
|
serverSideRpc |
||||
|
} = this.connector.configurationJson as MQTTLegacyBasicConfig; |
||||
|
let configurationJson = { |
||||
|
...this.connector.configurationJson, |
||||
|
requestsMapping: MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ |
||||
|
connectRequests, |
||||
|
disconnectRequests, |
||||
|
attributeRequests, |
||||
|
attributeUpdates, |
||||
|
serverSideRpc |
||||
|
}), |
||||
|
mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion((this.connector.configurationJson as MQTTLegacyBasicConfig).mapping), |
||||
|
}; |
||||
|
|
||||
|
this.mqttRequestTypeKeys.forEach((key: RequestType) => { |
||||
|
const { [key]: removedValue, ...rest } = configurationJson as MQTTLegacyBasicConfig; |
||||
|
configurationJson = { ...rest } as any; |
||||
|
}); |
||||
|
|
||||
|
this.cleanUpConfigJson(configurationJson as MQTTBasicConfig_v3_5_2); |
||||
|
|
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<MQTTBasicConfig_v3_5_2>; |
||||
|
} |
||||
|
|
||||
|
getDowngradedVersion(): GatewayConnector<MQTTLegacyBasicConfig> { |
||||
|
const { requestsMapping, mapping, ...restConfig } = this.connector.configurationJson as MQTTBasicConfig_v3_5_2; |
||||
|
|
||||
|
const updatedRequestsMapping = |
||||
|
MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record<RequestType, RequestMappingData[]>); |
||||
|
const updatedMapping = MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping); |
||||
|
|
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson: { |
||||
|
...restConfig, |
||||
|
...updatedRequestsMapping, |
||||
|
mapping: updatedMapping, |
||||
|
}, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<MQTTLegacyBasicConfig>; |
||||
|
} |
||||
|
|
||||
|
private cleanUpConfigJson(configurationJson: MQTTBasicConfig_v3_5_2): void { |
||||
|
if (isEqual(configurationJson.requestsMapping, {})) { |
||||
|
delete configurationJson.requestsMapping; |
||||
|
} |
||||
|
|
||||
|
if (isEqual(configurationJson.mapping, [])) { |
||||
|
delete configurationJson.mapping; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
GatewayConnector, LegacyServerConfig, |
||||
|
OPCBasicConfig, |
||||
|
OPCBasicConfig_v3_5_2, |
||||
|
OPCLegacyBasicConfig, |
||||
|
} from '../gateway-widget.models'; |
||||
|
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; |
||||
|
import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; |
||||
|
|
||||
|
export class OpcVersionProcessor extends GatewayConnectorVersionProcessor<OPCBasicConfig> { |
||||
|
|
||||
|
constructor( |
||||
|
protected gatewayVersionIn: string, |
||||
|
protected connector: GatewayConnector<OPCBasicConfig> |
||||
|
) { |
||||
|
super(gatewayVersionIn, connector); |
||||
|
} |
||||
|
|
||||
|
getUpgradedVersion(): GatewayConnector<OPCBasicConfig_v3_5_2> { |
||||
|
const server = this.connector.configurationJson.server as LegacyServerConfig; |
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson: { |
||||
|
server: server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(server) : {}, |
||||
|
mapping: server.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(server.mapping) : [], |
||||
|
}, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<OPCBasicConfig_v3_5_2>; |
||||
|
} |
||||
|
|
||||
|
getDowngradedVersion(): GatewayConnector<OPCLegacyBasicConfig> { |
||||
|
return { |
||||
|
...this.connector, |
||||
|
configurationJson: { |
||||
|
server: OpcVersionMappingUtil.mapServerToDowngradedVersion(this.connector.configurationJson as OPCBasicConfig_v3_5_2) |
||||
|
}, |
||||
|
configVersion: this.gatewayVersionIn |
||||
|
} as GatewayConnector<OPCLegacyBasicConfig>; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Directive } from '@angular/core'; |
||||
|
import { FormControl, FormGroup, ValidationErrors } from '@angular/forms'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { isEqual } from '@core/utils'; |
||||
|
import { GatewayConnectorBasicConfigDirective } from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; |
||||
|
import { |
||||
|
ModbusBasicConfig, |
||||
|
ModbusBasicConfig_v3_5_2, |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
|
||||
|
@Directive() |
||||
|
export abstract class ModbusBasicConfigDirective<BasicConfig> |
||||
|
extends GatewayConnectorBasicConfigDirective<ModbusBasicConfig_v3_5_2, BasicConfig> { |
||||
|
|
||||
|
enableSlaveControl: FormControl<boolean> = new FormControl(false); |
||||
|
|
||||
|
constructor() { |
||||
|
super(); |
||||
|
|
||||
|
this.enableSlaveControl.valueChanges |
||||
|
.pipe(takeUntil(this.destroy$)) |
||||
|
.subscribe(enable => { |
||||
|
this.updateSlaveEnabling(enable); |
||||
|
this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange }); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void { |
||||
|
super.writeValue(basicConfig); |
||||
|
this.onEnableSlaveControl(basicConfig); |
||||
|
} |
||||
|
|
||||
|
override validate(): ValidationErrors | null { |
||||
|
const { master, slave } = this.basicFormGroup.value; |
||||
|
const isEmpty = !master?.slaves?.length && (isEqual(slave, {}) || !slave); |
||||
|
if (!this.basicFormGroup.valid || isEmpty) { |
||||
|
return { basicFormGroup: { valid: false } }; |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
protected override initBasicFormGroup(): FormGroup { |
||||
|
return this.fb.group({ |
||||
|
master: [], |
||||
|
slave: [], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateSlaveEnabling(isEnabled: boolean): void { |
||||
|
if (isEnabled) { |
||||
|
this.basicFormGroup.get('slave').enable({ emitEvent: false }); |
||||
|
} else { |
||||
|
this.basicFormGroup.get('slave').disable({ emitEvent: false }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onEnableSlaveControl(basicConfig: ModbusBasicConfig): void { |
||||
|
this.enableSlaveControl.setValue(!!basicConfig.slave && !isEqual(basicConfig.slave, {})); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; |
||||
|
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { |
||||
|
ModbusBasicConfig_v3_5_2, |
||||
|
ModbusLegacyBasicConfig, ModbusLegacySlave, |
||||
|
ModbusMasterConfig, |
||||
|
ModbusSlave |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { ModbusSlaveConfigComponent } from '../modbus-slave-config/modbus-slave-config.component'; |
||||
|
import { ModbusMasterTableComponent } from '../modbus-master-table/modbus-master-table.component'; |
||||
|
import { EllipsisChipListDirective } from '@shared/directives/ellipsis-chip-list.directive'; |
||||
|
import { ModbusVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/modbus-version-mapping.util'; |
||||
|
import { |
||||
|
ModbusBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-modbus-legacy-basic-config', |
||||
|
templateUrl: './modbus-basic-config.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => ModbusLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
ModbusSlaveConfigComponent, |
||||
|
ModbusMasterTableComponent, |
||||
|
EllipsisChipListDirective, |
||||
|
], |
||||
|
styleUrls: ['./modbus-basic-config.component.scss'], |
||||
|
}) |
||||
|
export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective<ModbusLegacyBasicConfig> { |
||||
|
|
||||
|
protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { |
||||
|
return { |
||||
|
master: config.master?.slaves |
||||
|
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) |
||||
|
: { slaves: [] } as ModbusMasterConfig, |
||||
|
slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig { |
||||
|
return { |
||||
|
master: value.master, |
||||
|
slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -1,191 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; |
|
||||
import { |
|
||||
ControlValueAccessor, |
|
||||
FormBuilder, |
|
||||
FormGroup, |
|
||||
NG_VALIDATORS, |
|
||||
NG_VALUE_ACCESSOR, |
|
||||
ValidationErrors, |
|
||||
Validator, |
|
||||
} from '@angular/forms'; |
|
||||
import { |
|
||||
MappingType, |
|
||||
MQTTBasicConfig, |
|
||||
RequestMappingData, |
|
||||
RequestType, |
|
||||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|
||||
import { SharedModule } from '@shared/shared.module'; |
|
||||
import { CommonModule } from '@angular/common'; |
|
||||
import { takeUntil } from 'rxjs/operators'; |
|
||||
import { Subject } from 'rxjs'; |
|
||||
import { isObject } from 'lodash'; |
|
||||
import { |
|
||||
SecurityConfigComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
|
||||
import { |
|
||||
WorkersConfigControlComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; |
|
||||
import { |
|
||||
BrokerConfigControlComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; |
|
||||
import { |
|
||||
MappingTableComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
|
||||
import { isDefinedAndNotNull } from '@core/utils'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-mqtt-basic-config', |
|
||||
templateUrl: './mqtt-basic-config.component.html', |
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
|
||||
providers: [ |
|
||||
{ |
|
||||
provide: NG_VALUE_ACCESSOR, |
|
||||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|
||||
multi: true |
|
||||
}, |
|
||||
{ |
|
||||
provide: NG_VALIDATORS, |
|
||||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|
||||
multi: true |
|
||||
} |
|
||||
], |
|
||||
standalone: true, |
|
||||
imports: [ |
|
||||
CommonModule, |
|
||||
SharedModule, |
|
||||
SecurityConfigComponent, |
|
||||
WorkersConfigControlComponent, |
|
||||
BrokerConfigControlComponent, |
|
||||
MappingTableComponent, |
|
||||
], |
|
||||
styleUrls: ['./mqtt-basic-config.component.scss'] |
|
||||
}) |
|
||||
|
|
||||
export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { |
|
||||
|
|
||||
@Input() |
|
||||
generalTabContent: TemplateRef<any>; |
|
||||
|
|
||||
mappingTypes = MappingType; |
|
||||
basicFormGroup: FormGroup; |
|
||||
|
|
||||
private onChange: (value: MQTTBasicConfig) => void; |
|
||||
private onTouched: () => void; |
|
||||
|
|
||||
private destroy$ = new Subject<void>(); |
|
||||
|
|
||||
constructor(private fb: FormBuilder) { |
|
||||
this.basicFormGroup = this.fb.group({ |
|
||||
dataMapping: [], |
|
||||
requestsMapping: [], |
|
||||
broker: [], |
|
||||
workers: [], |
|
||||
}); |
|
||||
|
|
||||
this.basicFormGroup.valueChanges |
|
||||
.pipe(takeUntil(this.destroy$)) |
|
||||
.subscribe(value => { |
|
||||
this.onChange(this.getMappedMQTTConfig(value)); |
|
||||
this.onTouched(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
ngOnDestroy(): void { |
|
||||
this.destroy$.next(); |
|
||||
this.destroy$.complete(); |
|
||||
} |
|
||||
|
|
||||
registerOnChange(fn: (value: MQTTBasicConfig) => void): void { |
|
||||
this.onChange = fn; |
|
||||
} |
|
||||
|
|
||||
registerOnTouched(fn: () => void): void { |
|
||||
this.onTouched = fn; |
|
||||
} |
|
||||
|
|
||||
writeValue(basicConfig: MQTTBasicConfig): void { |
|
||||
const { broker, dataMapping = [], requestsMapping } = basicConfig; |
|
||||
const editedBase = { |
|
||||
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
|
||||
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
|
||||
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
|
||||
} : {}, |
|
||||
dataMapping: dataMapping || [], |
|
||||
broker: broker || {}, |
|
||||
requestsMapping: Array.isArray(requestsMapping) |
|
||||
? requestsMapping |
|
||||
: this.getRequestDataArray(requestsMapping), |
|
||||
}; |
|
||||
|
|
||||
this.basicFormGroup.setValue(editedBase, {emitEvent: false}); |
|
||||
} |
|
||||
|
|
||||
private getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { |
|
||||
let { broker, workers, dataMapping, requestsMapping } = basicConfig || {}; |
|
||||
|
|
||||
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
|
||||
broker = { |
|
||||
...broker, |
|
||||
...workers, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
if ((requestsMapping as RequestMappingData[])?.length) { |
|
||||
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingData[]); |
|
||||
} |
|
||||
|
|
||||
return { broker, workers, dataMapping, requestsMapping }; |
|
||||
} |
|
||||
|
|
||||
validate(): ValidationErrors | null { |
|
||||
return this.basicFormGroup.valid ? null : { |
|
||||
basicFormGroup: {valid: false} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
private getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): RequestMappingData[] { |
|
||||
const mappingConfigs = []; |
|
||||
|
|
||||
if (isObject(value)) { |
|
||||
Object.keys(value).forEach((configKey: string) => { |
|
||||
for (const mapping of value[configKey]) { |
|
||||
mappingConfigs.push({ |
|
||||
requestType: configKey, |
|
||||
requestValue: mapping |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return mappingConfigs; |
|
||||
} |
|
||||
|
|
||||
private getRequestDataObject(array: RequestMappingData[]): Record<RequestType, RequestMappingData[]> { |
|
||||
return array.reduce((result, { requestType, requestValue }) => { |
|
||||
result[requestType].push(requestValue); |
|
||||
return result; |
|
||||
}, { |
|
||||
connectRequests: [], |
|
||||
disconnectRequests: [], |
|
||||
attributeRequests: [], |
|
||||
attributeUpdates: [], |
|
||||
serverSideRpc: [], |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,82 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Directive } from '@angular/core'; |
||||
|
import { FormGroup } from '@angular/forms'; |
||||
|
import { |
||||
|
MappingType, |
||||
|
MQTTBasicConfig, MQTTBasicConfig_v3_5_2, |
||||
|
RequestMappingData, |
||||
|
RequestMappingValue, |
||||
|
RequestType |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { isObject } from '@core/utils'; |
||||
|
import { |
||||
|
GatewayConnectorBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; |
||||
|
|
||||
|
@Directive() |
||||
|
export abstract class MqttBasicConfigDirective<BasicConfig> |
||||
|
extends GatewayConnectorBasicConfigDirective<MQTTBasicConfig_v3_5_2, BasicConfig> { |
||||
|
|
||||
|
MappingType = MappingType; |
||||
|
|
||||
|
protected override initBasicFormGroup(): FormGroup { |
||||
|
return this.fb.group({ |
||||
|
mapping: [], |
||||
|
requestsMapping: [], |
||||
|
broker: [], |
||||
|
workers: [], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): RequestMappingData[] { |
||||
|
const mappingConfigs = []; |
||||
|
|
||||
|
if (isObject(value)) { |
||||
|
Object.keys(value).forEach((configKey: string) => { |
||||
|
for (const mapping of value[configKey]) { |
||||
|
mappingConfigs.push({ |
||||
|
requestType: configKey, |
||||
|
requestValue: mapping |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return mappingConfigs; |
||||
|
} |
||||
|
|
||||
|
protected getRequestDataObject(array: RequestMappingValue[]): Record<RequestType, RequestMappingValue[]> { |
||||
|
return array.reduce((result, { requestType, requestValue }) => { |
||||
|
result[requestType].push(requestValue); |
||||
|
return result; |
||||
|
}, { |
||||
|
connectRequests: [], |
||||
|
disconnectRequests: [], |
||||
|
attributeRequests: [], |
||||
|
attributeUpdates: [], |
||||
|
serverSideRpc: [], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
writeValue(basicConfig: BasicConfig): void { |
||||
|
this.basicFormGroup.setValue(this.mapConfigToFormValue(basicConfig), { emitEvent: false }); |
||||
|
} |
||||
|
|
||||
|
protected abstract override mapConfigToFormValue(config: BasicConfig): MQTTBasicConfig_v3_5_2; |
||||
|
protected abstract override getMappedValue(config: MQTTBasicConfig): BasicConfig; |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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, ChangeDetectionStrategy } from '@angular/core'; |
||||
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; |
||||
|
import { |
||||
|
BrokerConfig, |
||||
|
MQTTBasicConfig_v3_5_2, |
||||
|
RequestMappingData, |
||||
|
RequestMappingValue, |
||||
|
RequestType, WorkersConfig |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { |
||||
|
MqttBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { |
||||
|
SecurityConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
||||
|
import { |
||||
|
WorkersConfigControlComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; |
||||
|
import { |
||||
|
BrokerConfigControlComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; |
||||
|
import { |
||||
|
MappingTableComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-mqtt-basic-config', |
||||
|
templateUrl: './mqtt-basic-config.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => MqttBasicConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => MqttBasicConfigComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
styleUrls: ['./mqtt-basic-config.component.scss'], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
SecurityConfigComponent, |
||||
|
WorkersConfigControlComponent, |
||||
|
BrokerConfigControlComponent, |
||||
|
MappingTableComponent, |
||||
|
], |
||||
|
}) |
||||
|
export class MqttBasicConfigComponent extends MqttBasicConfigDirective<MQTTBasicConfig_v3_5_2> { |
||||
|
|
||||
|
protected override mapConfigToFormValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { |
||||
|
const { broker, mapping = [], requestsMapping } = basicConfig; |
||||
|
return{ |
||||
|
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
||||
|
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
||||
|
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
||||
|
} : {} as WorkersConfig, |
||||
|
mapping: mapping ?? [], |
||||
|
broker: broker ?? {} as BrokerConfig, |
||||
|
requestsMapping: this.getRequestDataArray(requestsMapping as Record<RequestType, RequestMappingData[]>), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTBasicConfig_v3_5_2 { |
||||
|
let { broker, workers, mapping, requestsMapping } = basicConfig || {}; |
||||
|
|
||||
|
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
||||
|
broker = { |
||||
|
...broker, |
||||
|
...workers, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if ((requestsMapping as RequestMappingData[])?.length) { |
||||
|
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); |
||||
|
} |
||||
|
|
||||
|
return { broker, mapping, requestsMapping }; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,124 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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, ChangeDetectionStrategy } from '@angular/core'; |
||||
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; |
||||
|
import { |
||||
|
BrokerConfig, |
||||
|
MQTTBasicConfig_v3_5_2, |
||||
|
MQTTLegacyBasicConfig, |
||||
|
RequestMappingData, |
||||
|
RequestMappingValue, |
||||
|
RequestType, WorkersConfig |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; |
||||
|
import { |
||||
|
MqttBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { |
||||
|
SecurityConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
||||
|
import { |
||||
|
WorkersConfigControlComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/workers-config-control/workers-config-control.component'; |
||||
|
import { |
||||
|
BrokerConfigControlComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; |
||||
|
import { |
||||
|
MappingTableComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-mqtt-legacy-basic-config', |
||||
|
templateUrl: './mqtt-basic-config.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
styleUrls: ['./mqtt-basic-config.component.scss'], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
SecurityConfigComponent, |
||||
|
WorkersConfigControlComponent, |
||||
|
BrokerConfigControlComponent, |
||||
|
MappingTableComponent, |
||||
|
], |
||||
|
}) |
||||
|
export class MqttLegacyBasicConfigComponent extends MqttBasicConfigDirective<MQTTLegacyBasicConfig> { |
||||
|
|
||||
|
protected override mapConfigToFormValue(config: MQTTLegacyBasicConfig): MQTTBasicConfig_v3_5_2 { |
||||
|
const { |
||||
|
broker, |
||||
|
mapping = [], |
||||
|
connectRequests = [], |
||||
|
disconnectRequests = [], |
||||
|
attributeRequests = [], |
||||
|
attributeUpdates = [], |
||||
|
serverSideRpc = [] |
||||
|
} = config as MQTTLegacyBasicConfig; |
||||
|
const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToUpgradedVersion({ |
||||
|
connectRequests, |
||||
|
disconnectRequests, |
||||
|
attributeRequests, |
||||
|
attributeUpdates, |
||||
|
serverSideRpc |
||||
|
}); |
||||
|
return { |
||||
|
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
||||
|
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
||||
|
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
||||
|
} : {} as WorkersConfig, |
||||
|
mapping: MqttVersionMappingUtil.mapMappingToUpgradedVersion(mapping) || [], |
||||
|
broker: broker || {} as BrokerConfig, |
||||
|
requestsMapping: this.getRequestDataArray(updatedRequestMapping), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected override getMappedValue(basicConfig: MQTTBasicConfig_v3_5_2): MQTTLegacyBasicConfig { |
||||
|
let { broker, workers, mapping, requestsMapping } = basicConfig || {}; |
||||
|
|
||||
|
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
||||
|
broker = { |
||||
|
...broker, |
||||
|
...workers, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if ((requestsMapping as RequestMappingData[])?.length) { |
||||
|
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
broker, |
||||
|
mapping: MqttVersionMappingUtil.mapMappingToDowngradedVersion(mapping), |
||||
|
...(MqttVersionMappingUtil.mapRequestsToDowngradedVersion(requestsMapping as Record<RequestType, RequestMappingData[]>)) |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -1,134 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; |
|
||||
import { |
|
||||
ControlValueAccessor, |
|
||||
FormBuilder, |
|
||||
FormGroup, |
|
||||
NG_VALIDATORS, |
|
||||
NG_VALUE_ACCESSOR, |
|
||||
ValidationErrors, |
|
||||
Validator, |
|
||||
} from '@angular/forms'; |
|
||||
import { |
|
||||
ConnectorType, |
|
||||
MappingType, |
|
||||
OPCBasicConfig, |
|
||||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|
||||
import { SharedModule } from '@shared/shared.module'; |
|
||||
import { CommonModule } from '@angular/common'; |
|
||||
import { takeUntil } from 'rxjs/operators'; |
|
||||
import { Subject } from 'rxjs'; |
|
||||
import { |
|
||||
SecurityConfigComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
|
||||
import { |
|
||||
WorkersConfigControlComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; |
|
||||
import { |
|
||||
BrokerConfigControlComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; |
|
||||
import { |
|
||||
MappingTableComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
|
||||
import { |
|
||||
OpcServerConfigComponent |
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-opc-ua-basic-config', |
|
||||
templateUrl: './opc-ua-basic-config.component.html', |
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
|
||||
providers: [ |
|
||||
{ |
|
||||
provide: NG_VALUE_ACCESSOR, |
|
||||
useExisting: forwardRef(() => OpcUaBasicConfigComponent), |
|
||||
multi: true |
|
||||
}, |
|
||||
{ |
|
||||
provide: NG_VALIDATORS, |
|
||||
useExisting: forwardRef(() => OpcUaBasicConfigComponent), |
|
||||
multi: true |
|
||||
} |
|
||||
], |
|
||||
standalone: true, |
|
||||
imports: [ |
|
||||
CommonModule, |
|
||||
SharedModule, |
|
||||
SecurityConfigComponent, |
|
||||
WorkersConfigControlComponent, |
|
||||
BrokerConfigControlComponent, |
|
||||
MappingTableComponent, |
|
||||
OpcServerConfigComponent, |
|
||||
], |
|
||||
styleUrls: ['./opc-ua-basic-config.component.scss'] |
|
||||
}) |
|
||||
|
|
||||
export class OpcUaBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { |
|
||||
@Input() generalTabContent: TemplateRef<any>; |
|
||||
|
|
||||
mappingTypes = MappingType; |
|
||||
basicFormGroup: FormGroup; |
|
||||
|
|
||||
onChange!: (value: string) => void; |
|
||||
onTouched!: () => void; |
|
||||
|
|
||||
protected readonly connectorType = ConnectorType; |
|
||||
private destroy$ = new Subject<void>(); |
|
||||
|
|
||||
constructor(private fb: FormBuilder) { |
|
||||
this.basicFormGroup = this.fb.group({ |
|
||||
mapping: [], |
|
||||
server: [], |
|
||||
}); |
|
||||
|
|
||||
this.basicFormGroup.valueChanges |
|
||||
.pipe(takeUntil(this.destroy$)) |
|
||||
.subscribe(value => { |
|
||||
this.onChange(value); |
|
||||
this.onTouched(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
ngOnDestroy(): void { |
|
||||
this.destroy$.next(); |
|
||||
this.destroy$.complete(); |
|
||||
} |
|
||||
|
|
||||
registerOnChange(fn: (value: string) => void): void { |
|
||||
this.onChange = fn; |
|
||||
} |
|
||||
|
|
||||
registerOnTouched(fn: () => void): void { |
|
||||
this.onTouched = fn; |
|
||||
} |
|
||||
|
|
||||
writeValue(basicConfig: OPCBasicConfig): void { |
|
||||
const editedBase = { |
|
||||
server: basicConfig.server || {}, |
|
||||
mapping: basicConfig.mapping || [], |
|
||||
}; |
|
||||
|
|
||||
this.basicFormGroup.setValue(editedBase, {emitEvent: false}); |
|
||||
} |
|
||||
|
|
||||
validate(): ValidationErrors | null { |
|
||||
return this.basicFormGroup.valid ? null : { |
|
||||
basicFormGroup: {valid: false} |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,88 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; |
||||
|
import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { |
||||
|
MappingType, |
||||
|
OPCBasicConfig_v3_5_2, |
||||
|
ServerConfig |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
||||
|
import { |
||||
|
SecurityConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
||||
|
import { |
||||
|
OpcServerConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; |
||||
|
import { |
||||
|
GatewayConnectorBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-opc-ua-basic-config', |
||||
|
templateUrl: './opc-ua-basic-config.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => OpcUaBasicConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => OpcUaBasicConfigComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
SecurityConfigComponent, |
||||
|
MappingTableComponent, |
||||
|
OpcServerConfigComponent, |
||||
|
], |
||||
|
styleUrls: ['./opc-ua-basic-config.component.scss'] |
||||
|
}) |
||||
|
export class OpcUaBasicConfigComponent extends GatewayConnectorBasicConfigDirective<OPCBasicConfig_v3_5_2, OPCBasicConfig_v3_5_2> { |
||||
|
|
||||
|
mappingTypes = MappingType; |
||||
|
isLegacy = false; |
||||
|
|
||||
|
protected override initBasicFormGroup(): FormGroup { |
||||
|
return this.fb.group({ |
||||
|
mapping: [], |
||||
|
server: [], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected override mapConfigToFormValue(config: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { |
||||
|
return { |
||||
|
server: config.server ?? {} as ServerConfig, |
||||
|
mapping: config.mapping ?? [], |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCBasicConfig_v3_5_2 { |
||||
|
return { |
||||
|
server: value.server, |
||||
|
mapping: value.mapping, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; |
||||
|
import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { |
||||
|
MappingType, |
||||
|
OPCBasicConfig_v3_5_2, |
||||
|
OPCLegacyBasicConfig, ServerConfig, |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { MappingTableComponent } from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
||||
|
import { |
||||
|
SecurityConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
||||
|
import { |
||||
|
OpcServerConfigComponent |
||||
|
} from '@home/components/widget/lib/gateway/connectors-configuration/opc/opc-server-config/opc-server-config.component'; |
||||
|
import { |
||||
|
GatewayConnectorBasicConfigDirective |
||||
|
} from '@home/components/widget/lib/gateway/abstract/gateway-connector-basic-config.abstract'; |
||||
|
import { OpcVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/opc-version-mapping.util'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-opc-ua-legacy-basic-config', |
||||
|
templateUrl: './opc-ua-basic-config.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => OpcUaLegacyBasicConfigComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
SecurityConfigComponent, |
||||
|
MappingTableComponent, |
||||
|
OpcServerConfigComponent, |
||||
|
], |
||||
|
styleUrls: ['./opc-ua-basic-config.component.scss'] |
||||
|
}) |
||||
|
export class OpcUaLegacyBasicConfigComponent extends GatewayConnectorBasicConfigDirective<OPCBasicConfig_v3_5_2, OPCLegacyBasicConfig> { |
||||
|
|
||||
|
mappingTypes = MappingType; |
||||
|
isLegacy = true; |
||||
|
|
||||
|
protected override initBasicFormGroup(): FormGroup { |
||||
|
return this.fb.group({ |
||||
|
mapping: [], |
||||
|
server: [], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected override mapConfigToFormValue(config: OPCLegacyBasicConfig): OPCBasicConfig_v3_5_2 { |
||||
|
return { |
||||
|
server: config.server ? OpcVersionMappingUtil.mapServerToUpgradedVersion(config.server) : {} as ServerConfig, |
||||
|
mapping: config.server?.mapping ? OpcVersionMappingUtil.mapMappingToUpgradedVersion(config.server.mapping) : [], |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected override getMappedValue(value: OPCBasicConfig_v3_5_2): OPCLegacyBasicConfig { |
||||
|
return { |
||||
|
server: OpcVersionMappingUtil.mapServerToDowngradedVersion(value), |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
ConnectorType, |
||||
|
GatewayConnector, |
||||
|
ModbusBasicConfig, |
||||
|
MQTTBasicConfig, |
||||
|
OPCBasicConfig, |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract'; |
||||
|
import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract'; |
||||
|
import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract'; |
||||
|
|
||||
|
export abstract class GatewayConnectorVersionMappingUtil { |
||||
|
|
||||
|
static getConfig(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { |
||||
|
switch(connector.type) { |
||||
|
case ConnectorType.MQTT: |
||||
|
return new MqttVersionProcessor(gatewayVersion, connector as GatewayConnector<MQTTBasicConfig>).getProcessedByVersion(); |
||||
|
case ConnectorType.OPCUA: |
||||
|
return new OpcVersionProcessor(gatewayVersion, connector as GatewayConnector<OPCBasicConfig>).getProcessedByVersion(); |
||||
|
case ConnectorType.MODBUS: |
||||
|
return new ModbusVersionProcessor(gatewayVersion, connector as GatewayConnector<ModbusBasicConfig>).getProcessedByVersion(); |
||||
|
default: |
||||
|
return connector; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
ModbusDataType, |
||||
|
ModbusLegacyRegisterValues, |
||||
|
ModbusLegacySlave, |
||||
|
ModbusMasterConfig, |
||||
|
ModbusRegisterValues, |
||||
|
ModbusSlave, |
||||
|
ModbusValue, |
||||
|
ModbusValues, |
||||
|
SlaveConfig |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
|
||||
|
export class ModbusVersionMappingUtil { |
||||
|
|
||||
|
static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { |
||||
|
return { |
||||
|
slaves: master.slaves.map((slave: SlaveConfig) => ({ |
||||
|
...slave, |
||||
|
deviceType: slave.deviceType ?? 'default', |
||||
|
})) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave { |
||||
|
if (!slave?.values) { |
||||
|
return slave as Omit<ModbusLegacySlave, 'values'>; |
||||
|
} |
||||
|
const values = Object.keys(slave.values).reduce((acc, valueKey) => { |
||||
|
acc = { |
||||
|
...acc, |
||||
|
[valueKey]: [ |
||||
|
slave.values[valueKey] |
||||
|
] |
||||
|
}; |
||||
|
return acc; |
||||
|
}, {} as ModbusLegacyRegisterValues); |
||||
|
return { |
||||
|
...slave, |
||||
|
values |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave { |
||||
|
if (!slave?.values) { |
||||
|
return slave as Omit<ModbusSlave, 'values'>; |
||||
|
} |
||||
|
const values = Object.keys(slave.values).reduce((acc, valueKey) => { |
||||
|
acc = { |
||||
|
...acc, |
||||
|
[valueKey]: this.mapValuesToUpgradedVersion(slave.values[valueKey][0]) |
||||
|
}; |
||||
|
return acc; |
||||
|
}, {} as ModbusRegisterValues); |
||||
|
return { |
||||
|
...slave, |
||||
|
values |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static mapValuesToUpgradedVersion(registerValues: ModbusValues): ModbusValues { |
||||
|
return Object.keys(registerValues).reduce((acc, valueKey) => { |
||||
|
acc = { |
||||
|
...acc, |
||||
|
[valueKey]: registerValues[valueKey].map((value: ModbusValue) => |
||||
|
({ ...value, type: (value.type as string) === 'int' ? ModbusDataType.INT16 : value.type })) |
||||
|
}; |
||||
|
return acc; |
||||
|
}, {} as ModbusValues); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,217 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { deleteNullProperties } from '@core/utils'; |
||||
|
import { |
||||
|
AttributeRequest, |
||||
|
ConnectorDeviceInfo, |
||||
|
Converter, |
||||
|
ConverterConnectorMapping, |
||||
|
ConvertorType, |
||||
|
LegacyConverter, |
||||
|
LegacyConverterConnectorMapping, |
||||
|
LegacyRequestMappingData, |
||||
|
RequestMappingData, |
||||
|
RequestType, |
||||
|
ServerSideRpc, |
||||
|
ServerSideRpcType, |
||||
|
SourceType |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
|
||||
|
export class MqttVersionMappingUtil { |
||||
|
|
||||
|
static readonly mqttRequestTypeKeys = Object.values(RequestType); |
||||
|
static readonly mqttRequestMappingOldFields = |
||||
|
['attributeNameJsonExpression', 'deviceNameJsonExpression', 'deviceNameTopicExpression', 'extension-config']; |
||||
|
static readonly mqttRequestMappingNewFields = |
||||
|
['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; |
||||
|
|
||||
|
static mapMappingToUpgradedVersion( |
||||
|
mapping: LegacyConverterConnectorMapping[] |
||||
|
): ConverterConnectorMapping[] { |
||||
|
return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { |
||||
|
const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); |
||||
|
|
||||
|
const newConverter = { |
||||
|
...converter, |
||||
|
deviceInfo, |
||||
|
extensionConfig: converter.extensionConfig || converter['extension-config'] || null |
||||
|
}; |
||||
|
|
||||
|
this.cleanUpOldFields(newConverter); |
||||
|
|
||||
|
return { converter: newConverter, topicFilter, subscriptionQos }; |
||||
|
}) as ConverterConnectorMapping[]; |
||||
|
} |
||||
|
|
||||
|
static mapRequestsToUpgradedVersion( |
||||
|
requestMapping: Record<RequestType, |
||||
|
LegacyRequestMappingData[]> |
||||
|
): Record<RequestType, RequestMappingData[]> { |
||||
|
return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { |
||||
|
if (!requestMapping[key]) { |
||||
|
return acc; |
||||
|
} |
||||
|
|
||||
|
acc[key] = requestMapping[key].map(value => { |
||||
|
const newValue = this.mapRequestToUpgradedVersion(value as LegacyRequestMappingData, key); |
||||
|
|
||||
|
this.cleanUpOldFields(newValue as {}); |
||||
|
|
||||
|
return newValue; |
||||
|
}); |
||||
|
|
||||
|
return acc; |
||||
|
}, {}) as Record<RequestType, RequestMappingData[]>; |
||||
|
} |
||||
|
|
||||
|
static mapRequestsToDowngradedVersion( |
||||
|
requestsMapping: Record<RequestType, RequestMappingData[]> |
||||
|
): Record<RequestType, LegacyRequestMappingData[]> { |
||||
|
return this.mqttRequestTypeKeys.reduce((acc, key) => { |
||||
|
if (!requestsMapping[key]) { |
||||
|
return acc; |
||||
|
} |
||||
|
|
||||
|
acc[key] = requestsMapping[key].map((value: RequestMappingData) => { |
||||
|
if (key === RequestType.SERVER_SIDE_RPC) { |
||||
|
delete (value as ServerSideRpc).type; |
||||
|
} |
||||
|
|
||||
|
const { attributeNameExpression, deviceInfo, ...rest } = value as AttributeRequest; |
||||
|
|
||||
|
const newValue = { |
||||
|
...rest, |
||||
|
attributeNameJsonExpression: attributeNameExpression || null, |
||||
|
deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource !== SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, |
||||
|
deviceNameTopicExpression: deviceInfo?.deviceNameExpressionSource === SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, |
||||
|
}; |
||||
|
|
||||
|
this.cleanUpNewFields(newValue); |
||||
|
|
||||
|
return newValue; |
||||
|
}); |
||||
|
|
||||
|
return acc; |
||||
|
}, {}) as Record<RequestType, LegacyRequestMappingData[]>; |
||||
|
} |
||||
|
|
||||
|
static mapMappingToDowngradedVersion( |
||||
|
mapping: ConverterConnectorMapping[] |
||||
|
): LegacyConverterConnectorMapping[] { |
||||
|
return mapping?.map((converterMapping: ConverterConnectorMapping) => { |
||||
|
const converter = this.mapConverterToDowngradedVersion(converterMapping.converter); |
||||
|
|
||||
|
this.cleanUpNewFields(converter as {}); |
||||
|
|
||||
|
return { converter, topicFilter: converterMapping.topicFilter }; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static mapConverterToDowngradedVersion(converter: Converter): LegacyConverter { |
||||
|
const { deviceInfo, ...restConverter } = converter; |
||||
|
|
||||
|
return converter.type !== ConvertorType.BYTES ? { |
||||
|
...restConverter, |
||||
|
deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource === SourceType.MSG ? deviceInfo.deviceNameExpression : null, |
||||
|
deviceTypeJsonExpression: |
||||
|
deviceInfo?.deviceProfileExpressionSource === SourceType.MSG ? deviceInfo.deviceProfileExpression : null, |
||||
|
deviceNameTopicExpression: |
||||
|
deviceInfo?.deviceNameExpressionSource !== SourceType.MSG |
||||
|
? deviceInfo?.deviceNameExpression |
||||
|
: null, |
||||
|
deviceTypeTopicExpression: deviceInfo?.deviceProfileExpressionSource !== SourceType.MSG |
||||
|
? deviceInfo?.deviceProfileExpression |
||||
|
: null, |
||||
|
} : { |
||||
|
...restConverter, |
||||
|
deviceNameExpression: deviceInfo.deviceNameExpression, |
||||
|
deviceTypeExpression: deviceInfo.deviceProfileExpression, |
||||
|
['extension-config']: converter.extensionConfig, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static cleanUpOldFields(obj: Record<string, unknown>): void { |
||||
|
this.mqttRequestMappingOldFields.forEach(field => delete obj[field]); |
||||
|
deleteNullProperties(obj); |
||||
|
} |
||||
|
|
||||
|
private static cleanUpNewFields(obj: Record<string, unknown>): void { |
||||
|
this.mqttRequestMappingNewFields.forEach(field => delete obj[field]); |
||||
|
deleteNullProperties(obj); |
||||
|
} |
||||
|
|
||||
|
private static getTypeSourceByValue(value: string): SourceType { |
||||
|
if (value.includes('${')) { |
||||
|
return SourceType.MSG; |
||||
|
} |
||||
|
if (value.includes(`/`)) { |
||||
|
return SourceType.TOPIC; |
||||
|
} |
||||
|
return SourceType.CONST; |
||||
|
} |
||||
|
|
||||
|
private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { |
||||
|
const deviceNameExpression = converter.deviceNameExpression |
||||
|
|| converter.deviceNameJsonExpression |
||||
|
|| converter.deviceNameTopicExpression |
||||
|
|| null; |
||||
|
const deviceNameExpressionSource = converter.deviceNameExpressionSource |
||||
|
? converter.deviceNameExpressionSource as SourceType |
||||
|
: deviceNameExpression ? this.getTypeSourceByValue(deviceNameExpression) : null; |
||||
|
const deviceProfileExpression = converter.deviceProfileExpression |
||||
|
|| converter.deviceTypeTopicExpression |
||||
|
|| converter.deviceTypeJsonExpression |
||||
|
|| 'default'; |
||||
|
const deviceProfileExpressionSource = converter.deviceProfileExpressionSource |
||||
|
? converter.deviceProfileExpressionSource as SourceType |
||||
|
: deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; |
||||
|
|
||||
|
return deviceNameExpression || deviceProfileExpression ? { |
||||
|
deviceNameExpression, |
||||
|
deviceNameExpressionSource, |
||||
|
deviceProfileExpression, |
||||
|
deviceProfileExpressionSource |
||||
|
} : null; |
||||
|
} |
||||
|
|
||||
|
private static mapRequestToUpgradedVersion(value, key: RequestType): RequestMappingData { |
||||
|
const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; |
||||
|
const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; |
||||
|
const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; |
||||
|
const attributeNameExpression = value.attributeNameExpressionSource || value.attributeNameJsonExpression || null; |
||||
|
const responseTopicQoS = key === RequestType.SERVER_SIDE_RPC ? 1 : null; |
||||
|
const type = key === RequestType.SERVER_SIDE_RPC |
||||
|
? (value as ServerSideRpc).responseTopicExpression |
||||
|
? ServerSideRpcType.WithResponse |
||||
|
: ServerSideRpcType.WithoutResponse |
||||
|
: null; |
||||
|
|
||||
|
return { |
||||
|
...value, |
||||
|
attributeNameExpression, |
||||
|
attributeNameExpressionSource: attributeNameExpression ? this.getTypeSourceByValue(attributeNameExpression) : null, |
||||
|
deviceInfo: value.deviceInfo ? value.deviceInfo : deviceNameExpression ? { |
||||
|
deviceNameExpression, |
||||
|
deviceNameExpressionSource: this.getTypeSourceByValue(deviceNameExpression), |
||||
|
deviceProfileExpression, |
||||
|
deviceProfileExpressionSource |
||||
|
} : null, |
||||
|
responseTopicQoS, |
||||
|
type |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,134 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
Attribute, |
||||
|
AttributesUpdate, |
||||
|
DeviceConnectorMapping, |
||||
|
LegacyAttribute, |
||||
|
LegacyDeviceAttributeUpdate, |
||||
|
LegacyDeviceConnectorMapping, |
||||
|
LegacyRpcMethod, |
||||
|
LegacyServerConfig, |
||||
|
LegacyTimeseries, |
||||
|
OPCBasicConfig_v3_5_2, |
||||
|
OPCUaSourceType, |
||||
|
RpcArgument, |
||||
|
RpcMethod, |
||||
|
ServerConfig, |
||||
|
Timeseries |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
|
||||
|
export class OpcVersionMappingUtil { |
||||
|
|
||||
|
static mapServerToUpgradedVersion(server: LegacyServerConfig): ServerConfig { |
||||
|
const { mapping, disableSubscriptions, ...restServer } = server; |
||||
|
return { |
||||
|
...restServer, |
||||
|
enableSubscriptions: !disableSubscriptions, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static mapServerToDowngradedVersion(config: OPCBasicConfig_v3_5_2): LegacyServerConfig { |
||||
|
const { mapping, server } = config; |
||||
|
const { enableSubscriptions, ...restServer } = server; |
||||
|
return { |
||||
|
...restServer, |
||||
|
mapping: mapping ? this.mapMappingToDowngradedVersion(mapping) : [], |
||||
|
disableSubscriptions: !enableSubscriptions, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] { |
||||
|
return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({ |
||||
|
...legacyMapping, |
||||
|
deviceNodeSource: this.getTypeSourceByValue(legacyMapping.deviceNodePattern), |
||||
|
deviceInfo: { |
||||
|
deviceNameExpression: legacyMapping.deviceNamePattern, |
||||
|
deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern), |
||||
|
deviceProfileExpression: legacyMapping.deviceTypePattern ?? 'default', |
||||
|
deviceProfileExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceTypePattern ?? 'default'), |
||||
|
}, |
||||
|
attributes: legacyMapping.attributes.map((attribute: LegacyAttribute) => ({ |
||||
|
key: attribute.key, |
||||
|
type: this.getTypeSourceByValue(attribute.path), |
||||
|
value: attribute.path, |
||||
|
})), |
||||
|
attributes_updates: legacyMapping.attributes_updates.map((attributeUpdate: LegacyDeviceAttributeUpdate) => ({ |
||||
|
key: attributeUpdate.attributeOnThingsBoard, |
||||
|
type: this.getTypeSourceByValue(attributeUpdate.attributeOnDevice), |
||||
|
value: attributeUpdate.attributeOnDevice, |
||||
|
})), |
||||
|
timeseries: legacyMapping.timeseries.map((timeseries: LegacyTimeseries) => ({ |
||||
|
key: timeseries.key, |
||||
|
type: this.getTypeSourceByValue(timeseries.path), |
||||
|
value: timeseries.path, |
||||
|
})), |
||||
|
rpc_methods: legacyMapping.rpc_methods.map((rpcMethod: LegacyRpcMethod) => ({ |
||||
|
method: rpcMethod.method, |
||||
|
arguments: rpcMethod.arguments.map(arg => ({ |
||||
|
value: arg, |
||||
|
type: this.getArgumentType(arg), |
||||
|
} as RpcArgument)) |
||||
|
})) |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
static mapMappingToDowngradedVersion(mapping: DeviceConnectorMapping[]): LegacyDeviceConnectorMapping[] { |
||||
|
return mapping.map((upgradedMapping: DeviceConnectorMapping) => ({ |
||||
|
...upgradedMapping, |
||||
|
deviceNamePattern: upgradedMapping.deviceInfo.deviceNameExpression, |
||||
|
deviceTypePattern: upgradedMapping.deviceInfo.deviceProfileExpression, |
||||
|
attributes: upgradedMapping.attributes.map((attribute: Attribute) => ({ |
||||
|
key: attribute.key, |
||||
|
path: attribute.value, |
||||
|
})), |
||||
|
attributes_updates: upgradedMapping.attributes_updates.map((attributeUpdate: AttributesUpdate) => ({ |
||||
|
attributeOnThingsBoard: attributeUpdate.key, |
||||
|
attributeOnDevice: attributeUpdate.value, |
||||
|
})), |
||||
|
timeseries: upgradedMapping.timeseries.map((timeseries: Timeseries) => ({ |
||||
|
key: timeseries.key, |
||||
|
path: timeseries.value, |
||||
|
})), |
||||
|
rpc_methods: upgradedMapping.rpc_methods.map((rpcMethod: RpcMethod) => ({ |
||||
|
method: rpcMethod.method, |
||||
|
arguments: rpcMethod.arguments.map((arg: RpcArgument) => arg.value) |
||||
|
})) |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
private static getTypeSourceByValue(value: string): OPCUaSourceType { |
||||
|
if (value.includes('${')) { |
||||
|
return OPCUaSourceType.IDENTIFIER; |
||||
|
} |
||||
|
if (value.includes(`/`) || value.includes('\\')) { |
||||
|
return OPCUaSourceType.PATH; |
||||
|
} |
||||
|
return OPCUaSourceType.CONST; |
||||
|
} |
||||
|
|
||||
|
private static getArgumentType(arg: unknown): string { |
||||
|
switch (typeof arg) { |
||||
|
case 'boolean': |
||||
|
return 'boolean'; |
||||
|
case 'number': |
||||
|
return Number.isInteger(arg) ? 'integer' : 'float'; |
||||
|
default: |
||||
|
return 'string'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2024 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]="datapointsLimitFormGroup" class="limit-slider-container" fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<mat-slider fxFlex |
||||
|
min="{{minDatapointsLimit()}}" |
||||
|
max="{{maxDatapointsLimit()}}"> |
||||
|
<input matSliderThumb formControlName="limit" [value]="datapointsLimitFormGroup.get('limit').value"/> |
||||
|
</mat-slider> |
||||
|
<mat-form-field class="limit-slider-value" subscriptSizing="dynamic" appearance="outline"> |
||||
|
<input matInput formControlName="limit" type="number" step="1" |
||||
|
[value]="datapointsLimitFormGroup.get('limit').value" |
||||
|
min="{{minDatapointsLimit()}}" |
||||
|
max="{{maxDatapointsLimit()}}"/> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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 "../../../../scss/constants"; |
||||
|
|
||||
|
.limit-slider-container { |
||||
|
width: 100%; |
||||
|
.limit-slider-value { |
||||
|
margin-left: 16px; |
||||
|
min-width: 25px; |
||||
|
max-width: 106px; |
||||
|
} |
||||
|
mat-form-field input[type=number] { |
||||
|
text-align: center; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
.limit-slider-container { |
||||
|
> label { |
||||
|
margin-right: 16px; |
||||
|
width: min-content; |
||||
|
max-width: 40%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { TimeService } from '@core/services/time.service'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-datapoints-limit', |
||||
|
templateUrl: './datapoints-limit.component.html', |
||||
|
styleUrls: ['./datapoints-limit.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => DatapointsLimitComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => DatapointsLimitComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class DatapointsLimitComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { |
||||
|
|
||||
|
datapointsLimitFormGroup: FormGroup; |
||||
|
|
||||
|
modelValue: number | null; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
const newVal = coerceBooleanProperty(value); |
||||
|
if (this.requiredValue !== newVal) { |
||||
|
this.requiredValue = newVal; |
||||
|
this.updateValidators(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
private destroy$ = new Subject<void>(); |
||||
|
|
||||
|
constructor(private fb: FormBuilder, |
||||
|
private timeService: TimeService) { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.datapointsLimitFormGroup = this.fb.group({ |
||||
|
limit: [null, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]] |
||||
|
}); |
||||
|
this.datapointsLimitFormGroup.get('limit').valueChanges.pipe( |
||||
|
takeUntil(this.destroy$) |
||||
|
).subscribe((value) => { |
||||
|
this.updateView(value); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
updateValidators() { |
||||
|
if (this.datapointsLimitFormGroup) { |
||||
|
if (this.required) { |
||||
|
this.datapointsLimitFormGroup.get('limit').addValidators(Validators.required); |
||||
|
} else { |
||||
|
this.datapointsLimitFormGroup.get('limit').removeValidators(Validators.required); |
||||
|
} |
||||
|
this.datapointsLimitFormGroup.get('limit').updateValueAndValidity(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.datapointsLimitFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.datapointsLimitFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private checkLimit(limit?: number): number { |
||||
|
if (!limit || limit < this.minDatapointsLimit()) { |
||||
|
return this.minDatapointsLimit(); |
||||
|
} else if (limit > this.maxDatapointsLimit()) { |
||||
|
return this.maxDatapointsLimit(); |
||||
|
} |
||||
|
return limit; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: number | null): void { |
||||
|
this.modelValue = this.checkLimit(value); |
||||
|
this.datapointsLimitFormGroup.patchValue( |
||||
|
{ limit: this.modelValue }, {emitEvent: false} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
updateView(value: number | null) { |
||||
|
if (this.modelValue !== value) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors { |
||||
|
return this.datapointsLimitFormGroup.get('limit').valid ? null : { |
||||
|
datapointsLimitFormGroup: false, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
minDatapointsLimit() { |
||||
|
return this.timeService.getMinDatapointsLimit(); |
||||
|
} |
||||
|
|
||||
|
maxDatapointsLimit() { |
||||
|
return this.timeService.getMaxDatapointsLimit(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue