From 0867030cba748aabe041a91e58a50e87d923a25f Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Sep 2023 14:58:33 +0300 Subject: [PATCH 001/137] Add device state node implementation --- .../queue/DefaultTbCoreConsumerService.java | 54 ++- .../state/DefaultDeviceStateService.java | 54 +-- .../service/state/DeviceStateService.java | 2 + .../src/main/resources/thingsboard.yml | 6 +- .../state/DefaultDeviceStateServiceTest.java | 171 +++++++++- common/cluster-api/src/main/proto/queue.proto | 24 ++ .../rule/engine/action/TbDeviceStateNode.java | 178 ++++++++++ .../TbDeviceStateNodeConfiguration.java | 34 ++ .../engine/action/TbDeviceStateNodeTest.java | 313 ++++++++++++++++++ 9 files changed, 797 insertions(+), 39 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index fd5a252cc5..02850e9d8c 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -256,14 +256,23 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); if (actorMsg.isPresent()) { @@ -592,17 +601,54 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> stats = new HashMap<>(); for (DeviceStateData stateData : deviceStates.values()) { Pair tenantDevicesActivity = stats.computeIfAbsent(stateData.getTenantId(), @@ -489,10 +500,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(List.of(deviceIdInfo), 0, 1, false)); } + @Test + public void givenDeviceBelongsToExternalPartition_whenOnDeviceInactivity_thenCleansStateAndDoesNotReportInactivity() { + // GIVEN + service.deviceStates.put(deviceId, DeviceStateData.builder().build()); + given(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)) + .willReturn(TopicPartitionInfo.builder().build()); + + // WHEN + service.onDeviceInactivity(tenantId, deviceId); + + // THEN + assertThat(service.deviceStates).isEmpty(); + then(service).should(never()).fetchDeviceStateDataUsingSeparateRequests(deviceId); + then(clusterService).shouldHaveNoInteractions(); + then(notificationRuleProcessor).shouldHaveNoInteractions(); + then(telemetrySubscriptionService).shouldHaveNoInteractions(); + } + + @Test + public void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() throws InterruptedException { + // GIVEN + initStateService(10000); + var deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(DeviceState.builder().build()) + .metaData(new TbMsgMetaData()) + .build(); + + service.deviceStates.put(deviceId, deviceStateData); + + // WHEN + long timeBeforeCall = System.currentTimeMillis(); + service.onDeviceInactivity(tenantId, deviceId); + long timeAfterCall = System.currentTimeMillis(); + + // THEN + var inactivityTimeCaptor = ArgumentCaptor.forClass(Long.class); + then(telemetrySubscriptionService).should().saveAttrAndNotify( + any(), eq(deviceId), any(), eq(INACTIVITY_ALARM_TIME), inactivityTimeCaptor.capture(), any() + ); + var actualInactivityTime = inactivityTimeCaptor.getValue(); + assertThat(actualInactivityTime).isGreaterThanOrEqualTo(timeBeforeCall); + assertThat(actualInactivityTime).isLessThanOrEqualTo(timeAfterCall); + + then(telemetrySubscriptionService).should().saveAttrAndNotify( + any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(false), any() + ); + + var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); + var actualMsg = msgCaptor.getValue(); + assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); + assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); + + var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); + then(notificationRuleProcessor).should().process(notificationCaptor.capture()); + var actualNotification = notificationCaptor.getValue(); + assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); + assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); + assertThat(actualNotification.isActive()).isFalse(); + } + + @Test + public void givenInactivityTimeoutReached_whenUpdateInactivityStateIfExpired_thenReportsInactivity() { + // GIVEN + var deviceStateData = DeviceStateData.builder() + .tenantId(tenantId) + .deviceId(deviceId) + .state(DeviceState.builder().build()) + .metaData(new TbMsgMetaData()) + .build(); + + given(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).willReturn(tpi); + + // WHEN + service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); + + // THEN + then(telemetrySubscriptionService).should().saveAttrAndNotify( + any(), eq(deviceId), any(), eq(INACTIVITY_ALARM_TIME), anyLong(), any() + ); + then(telemetrySubscriptionService).should().saveAttrAndNotify( + any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(false), any() + ); + + var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); + var actualMsg = msgCaptor.getValue(); + assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); + assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); + + var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); + then(notificationRuleProcessor).should().process(notificationCaptor.capture()); + var actualNotification = notificationCaptor.getValue(); + assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); + assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); + assertThat(actualNotification.isActive()).isFalse(); + } + @Test public void givenDeviceIdFromDeviceStatesMap_whenGetOrFetchDeviceStateData_thenNoStackOverflow() { service.deviceStates.put(deviceId, deviceStateDataMock); DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); - assertThat(deviceStateData, is(deviceStateDataMock)); - Mockito.verify(service, never()).fetchDeviceStateDataUsingEntityDataQuery(deviceId); + assertThat(deviceStateData).isEqualTo(deviceStateDataMock); + Mockito.verify(service, never()).fetchDeviceStateDataUsingSeparateRequests(deviceId); } @Test public void givenDeviceIdWithoutDeviceStateInMap_whenGetOrFetchDeviceStateData_thenFetchDeviceStateData() { service.deviceStates.clear(); - willReturn(deviceStateDataMock).given(service).fetchDeviceStateDataUsingEntityDataQuery(deviceId); + willReturn(deviceStateDataMock).given(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); - assertThat(deviceStateData, is(deviceStateDataMock)); - Mockito.verify(service, times(1)).fetchDeviceStateDataUsingEntityDataQuery(deviceId); + assertThat(deviceStateData).isEqualTo(deviceStateDataMock); + Mockito.verify(service, times(1)).fetchDeviceStateDataUsingSeparateRequests(deviceId); } @Test @@ -341,4 +459,37 @@ public class DefaultDeviceStateServiceTest { Mockito.verify(telemetrySubscriptionService, Mockito.times(1)).saveAttrAndNotify(Mockito.any(), Mockito.eq(deviceId), Mockito.any(), Mockito.eq("active"), Mockito.eq(isActive), Mockito.any()); } -} \ No newline at end of file + @Test + public void givenConcurrentAccess_whenGetOrFetchDeviceStateData_thenFetchDeviceStateDataInvokedOnce() { + var deviceStateData = DeviceStateData.builder().build(); + var getOrFetchInvocationCounter = new AtomicInteger(); + + doAnswer(invocation -> { + getOrFetchInvocationCounter.incrementAndGet(); + Thread.sleep(100); + return deviceStateData; + }).when(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); + + int numberOfThreads = 10; + var allThreadsReadyLatch = new CountDownLatch(numberOfThreads); + var executor = Executors.newFixedThreadPool(numberOfThreads); + + for (int i = 0; i < numberOfThreads; i++) { + executor.submit(() -> { + allThreadsReadyLatch.countDown(); + try { + allThreadsReadyLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + service.getOrFetchDeviceStateData(deviceId); + }); + } + + executor.shutdown(); + await().atMost(10, TimeUnit.SECONDS).until(executor::isTerminated); + + assertThat(getOrFetchInvocationCounter.get()).isEqualTo(1); + } + +} diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 60dfcae1de..5260a4fa26 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -480,6 +480,13 @@ message GetOtaPackageResponseMsg { string fileName = 8; } +message DeviceConnectProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; +} + message DeviceActivityProto { int64 tenantIdMSB = 1; int64 tenantIdLSB = 2; @@ -488,6 +495,20 @@ message DeviceActivityProto { int64 lastActivityTime = 5; } +message DeviceDisconnectProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; +} + +message DeviceInactivityProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; +} + //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { int64 lastActivityTime = 1; @@ -977,6 +998,9 @@ message ToCoreMsg { NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; LifecycleEventProto lifecycleEventMsg = 8; ErrorEventProto errorEventMsg = 9; + DeviceConnectProto deviceConnectMsg = 10; + DeviceDisconnectProto deviceDisconnectMsg = 11; + DeviceInactivityProto deviceInactivityMsg = 12; } /* High priority messages with low latency are handled by ThingsBoard Core Service separately */ diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java new file mode 100644 index 0000000000..7003f78a00 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -0,0 +1,178 @@ +/** + * Copyright © 2016-2023 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.action; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; + +import java.util.Map; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "device state", + nodeDescription = "Triggers device connectivity events", + nodeDetails = "If incoming message originator is a device," + + " registers configured event for that device in the Device State Service," + + " which sends appropriate message to the Rule Engine. " + + "Incoming message is forwarded using the Success chain," + + " unless an unexpected error occurs during message processing" + + " then incoming message is forwarded using the Failure chain." + + "
" + + "Supported device connectivity events are:" + + "
    " + + "
  • Connect event
  • " + + "
  • Disconnect event
  • " + + "
  • Activity event
  • " + + "
  • Inactivity event
  • " + + "
" + + "This node is particularly useful when device isn't using transports to receive data," + + " such as when fetching data from external API or computing new data within the rule chain.", + configClazz = TbDeviceStateNodeConfiguration.class, + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeDeviceStateConfig" +) +public class TbDeviceStateNode implements TbNode { + + private static final TbQueueCallback EMPTY_CALLBACK = new TbQueueCallback() { + + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + } + + @Override + public void onFailure(Throwable t) { + } + + }; + + private final Map SUPPORTED_EVENTS = Map.of( + TbMsgType.CONNECT_EVENT, this::sendDeviceConnectMsg, + TbMsgType.ACTIVITY_EVENT, this::sendDeviceActivityMsg, + TbMsgType.DISCONNECT_EVENT, this::sendDeviceDisconnectMsg, + TbMsgType.INACTIVITY_EVENT, this::sendDeviceInactivityMsg + ); + + private interface ConnectivityEvent { + + void sendEvent(TbContext ctx, TbMsg msg); + + } + + private TbMsgType event; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + TbMsgType event = TbNodeUtils.convert(configuration, TbDeviceStateNodeConfiguration.class).getEvent(); + if (event == null) { + throw new TbNodeException("Event cannot be null!", true); + } + if (!SUPPORTED_EVENTS.containsKey(event)) { + throw new TbNodeException("Unsupported event: " + event, true); + } + this.event = event; + + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + var originator = msg.getOriginator(); + if (!ctx.isLocalEntity(originator)) { + log.warn("[{}][device-state-node] Received message from non-local entity [{}]!", ctx.getSelfId(), originator); + return; + } + if (!EntityType.DEVICE.equals(originator.getEntityType())) { + ctx.tellSuccess(msg); + return; + } + SUPPORTED_EVENTS.get(event).sendEvent(ctx, msg); + ctx.tellSuccess(msg); + } + + private void sendDeviceConnectMsg(TbContext ctx, TbMsg msg) { + var tenantUuid = ctx.getTenantId().getId(); + var deviceUuid = msg.getOriginator().getId(); + var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(deviceConnectMsg) + .build(); + ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + } + + private void sendDeviceActivityMsg(TbContext ctx, TbMsg msg) { + var tenantUuid = ctx.getTenantId().getId(); + var deviceUuid = msg.getOriginator().getId(); + var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastActivityTime(System.currentTimeMillis()) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(deviceActivityMsg) + .build(); + ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + } + + private void sendDeviceDisconnectMsg(TbContext ctx, TbMsg msg) { + var tenantUuid = ctx.getTenantId().getId(); + var deviceUuid = msg.getOriginator().getId(); + var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(deviceDisconnectMsg) + .build(); + ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + } + + private void sendDeviceInactivityMsg(TbContext ctx, TbMsg msg) { + var tenantUuid = ctx.getTenantId().getId(); + var deviceUuid = msg.getOriginator().getId(); + var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(deviceInactivityMsg) + .build(); + ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java new file mode 100644 index 0000000000..c89fcb076f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 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.action; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.msg.TbMsgType; + +@Data +public class TbDeviceStateNodeConfiguration implements NodeConfiguration { + + private TbMsgType event; + + @Override + public TbDeviceStateNodeConfiguration defaultConfiguration() { + var config = new TbDeviceStateNodeConfiguration(); + config.setEvent(TbMsgType.ACTIVITY_EVENT); + return config; + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java new file mode 100644 index 0000000000..1aaabce61d --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -0,0 +1,313 @@ +/** + * Copyright © 2016-2023 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.action; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +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.then; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +public class TbDeviceStateNodeTest { + + private static TenantId DUMMY_TENANT_ID; + private static TbMsg DUMMY_MSG; + private static DeviceId DUMMY_MSG_ORIGINATOR; + @Mock + private TbContext ctxMock; + @Mock + private TbClusterService tbClusterServiceMock; + private TbDeviceStateNode node; + private TbDeviceStateNodeConfiguration config; + + @BeforeAll + public static void init() { + DUMMY_TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); + + var device = new Device(); + device.setTenantId(DUMMY_TENANT_ID); + device.setId(new DeviceId(UUID.randomUUID())); + device.setName("My humidity sensor"); + device.setType("Humidity sensor"); + device.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID())); + var metaData = new TbMsgMetaData(); + metaData.putValue("deviceName", device.getName()); + metaData.putValue("deviceType", device.getType()); + metaData.putValue("ts", String.valueOf(System.currentTimeMillis())); + var data = JacksonUtil.newObjectNode(); + data.put("humidity", 58.3); + DUMMY_MSG = TbMsg.newMsg( + TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), metaData, JacksonUtil.toString(data) + ); + DUMMY_MSG_ORIGINATOR = device.getId(); + } + + @BeforeEach + public void setUp() { + node = new TbDeviceStateNode(); + config = new TbDeviceStateNodeConfiguration(); + } + + @Test + public void givenDefaultConfiguration_whenInvoked_thenCorrectValuesAreSet() { + // GIVEN-WHEN + config = config.defaultConfiguration(); + + // THEN + assertThat(config.getEvent()).isEqualTo(TbMsgType.ACTIVITY_EVENT); + } + + @Test + public void givenNullEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException() { + // GIVEN + config.setEvent(null); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN-THEN + assertThatThrownBy(() -> node.init(ctxMock, nodeConfig)) + .isInstanceOf(TbNodeException.class) + .hasMessage("Event cannot be null!") + .matches(e -> ((TbNodeException) e).isUnrecoverable()); + } + + @Test + public void givenUnsupportedEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException() { + // GIVEN + var unsupportedEvent = TbMsgType.TO_SERVER_RPC_REQUEST; + config.setEvent(unsupportedEvent); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + + // WHEN-THEN + assertThatThrownBy(() -> node.init(ctxMock, nodeConfig)) + .isInstanceOf(TbNodeException.class) + .hasMessage("Unsupported event: " + unsupportedEvent) + .matches(e -> ((TbNodeException) e).isUnrecoverable()); + } + + @Test + public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { + // GIVEN + var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, new AssetId(UUID.randomUUID()), new TbMsgMetaData(), "{}"); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + then(ctxMock).should(only()).tellSuccess(msg); + } + + @Test + public void givenNonLocalOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { + // GIVEN + given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(false); + + // WHEN + node.onMsg(ctxMock, DUMMY_MSG); + + // THEN + then(ctxMock).should(times(1)).isLocalEntity(DUMMY_MSG_ORIGINATOR); + then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + + @Test + public void givenConnectEventInConfig_whenOnMsg_thenOnDeviceConnectCalledAndTellsSuccess() throws TbNodeException { + // GIVEN + config.setEvent(TbMsgType.CONNECT_EVENT); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfig); + given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); + given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); + given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); + + // WHEN + node.onMsg(ctxMock, DUMMY_MSG); + + // THEN + var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); + then(tbClusterServiceMock).should(times(1)) + .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); + + TransportProtos.DeviceConnectProto expectedDeviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) + .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) + .build(); + TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(expectedDeviceConnectMsg) + .build(); + assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); + + then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + + @Test + public void givenActivityEventInConfig_whenOnMsg_thenOnDeviceActivityCalledWithCorrectTimeAndTellsSuccess() + throws TbNodeException { + // GIVEN + config.setEvent(TbMsgType.ACTIVITY_EVENT); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfig); + given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); + given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); + given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); + + + // WHEN + long timeBeforeCall = System.currentTimeMillis(); + node.onMsg(ctxMock, DUMMY_MSG); + long timeAfterCall = System.currentTimeMillis(); + + // THEN + var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); + then(tbClusterServiceMock).should(times(1)) + .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); + + TransportProtos.ToCoreMsg actualToCoreMsg = protoCaptor.getValue(); + long actualLastActivityTime = actualToCoreMsg.getDeviceActivityMsg().getLastActivityTime(); + + assertThat(actualLastActivityTime).isGreaterThanOrEqualTo(timeBeforeCall); + assertThat(actualLastActivityTime).isLessThanOrEqualTo(timeAfterCall); + + TransportProtos.DeviceActivityProto updatedActivityMsg = actualToCoreMsg.getDeviceActivityMsg().toBuilder() + .setLastActivityTime(123L) + .build(); + TransportProtos.ToCoreMsg updatedToCoreMsg = actualToCoreMsg.toBuilder() + .setDeviceActivityMsg(updatedActivityMsg) + .build(); + + TransportProtos.DeviceActivityProto expectedDeviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) + .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) + .setLastActivityTime(123L) + .build(); + TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(expectedDeviceActivityMsg) + .build(); + + assertThat(updatedToCoreMsg).isEqualTo(expectedToCoreMsg); + + then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + + @Test + public void givenInactivityEventInConfig_whenOnMsg_thenOnDeviceInactivityCalledAndTellsSuccess() + throws TbNodeException { + // GIVEN + config.setEvent(TbMsgType.INACTIVITY_EVENT); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfig); + given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); + given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); + given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); + + // WHEN + node.onMsg(ctxMock, DUMMY_MSG); + + // THEN + var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); + then(tbClusterServiceMock).should(times(1)) + .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); + + TransportProtos.DeviceInactivityProto expectedDeviceInactivityMsg = + TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) + .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) + .build(); + TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(expectedDeviceInactivityMsg) + .build(); + assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); + + then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + + @Test + public void givenDisconnectEventInConfig_whenOnMsg_thenOnDeviceDisconnectCalledAndTellsSuccess() + throws TbNodeException { + // GIVEN + config.setEvent(TbMsgType.DISCONNECT_EVENT); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfig); + given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); + given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); + given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); + + // WHEN + node.onMsg(ctxMock, DUMMY_MSG); + + // THEN + var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); + then(tbClusterServiceMock).should(times(1)) + .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); + + TransportProtos.DeviceDisconnectProto expectedDeviceDisconnectMsg = + TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) + .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) + .build(); + TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(expectedDeviceDisconnectMsg) + .build(); + assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); + + then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + +} From 0c5e7a933cde78a877c1adc84ba126a41dec4bdf Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Sep 2023 18:30:13 +0300 Subject: [PATCH 002/137] Fix test --- .../engine/action/TbDeviceStateNodeTest.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 1aaabce61d..123dffd0ea 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -41,11 +41,11 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.only; import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) @@ -128,13 +128,17 @@ public class TbDeviceStateNodeTest { @Test public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { // GIVEN - var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, new AssetId(UUID.randomUUID()), new TbMsgMetaData(), "{}"); + var originator = new AssetId(UUID.randomUUID()); + var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, originator, new TbMsgMetaData(), "{}"); + given(ctxMock.isLocalEntity(originator)).willReturn(true); // WHEN node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should(only()).tellSuccess(msg); + then(ctxMock).should(times(1)).isLocalEntity(originator); + then(ctxMock).should(times(1)).tellSuccess(msg); + then(ctxMock).shouldHaveNoMoreInteractions(); } @Test @@ -147,16 +151,20 @@ public class TbDeviceStateNodeTest { // THEN then(ctxMock).should(times(1)).isLocalEntity(DUMMY_MSG_ORIGINATOR); - then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); + then(ctxMock).should().getSelfId(); then(ctxMock).shouldHaveNoMoreInteractions(); } @Test - public void givenConnectEventInConfig_whenOnMsg_thenOnDeviceConnectCalledAndTellsSuccess() throws TbNodeException { + public void givenConnectEventInConfig_whenOnMsg_thenOnDeviceConnectCalledAndTellsSuccess() { // GIVEN config.setEvent(TbMsgType.CONNECT_EVENT); var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctxMock, nodeConfig); + try { + node.init(ctxMock, nodeConfig); + } catch (TbNodeException e) { + fail("Node failed to initialize!", e); + } given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); From 51aae476d35f73537a139d019af397a4240e83c5 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 12 Sep 2023 13:40:58 +0300 Subject: [PATCH 003/137] Move telling success into queue callback --- .../rule/engine/action/TbDeviceStateNode.java | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 7003f78a00..8adafe8d62 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -59,18 +59,6 @@ import java.util.Map; ) public class TbDeviceStateNode implements TbNode { - private static final TbQueueCallback EMPTY_CALLBACK = new TbQueueCallback() { - - @Override - public void onSuccess(TbQueueMsgMetadata metadata) { - } - - @Override - public void onFailure(Throwable t) { - } - - }; - private final Map SUPPORTED_EVENTS = Map.of( TbMsgType.CONNECT_EVENT, this::sendDeviceConnectMsg, TbMsgType.ACTIVITY_EVENT, this::sendDeviceActivityMsg, @@ -96,7 +84,6 @@ public class TbDeviceStateNode implements TbNode { throw new TbNodeException("Unsupported event: " + event, true); } this.event = event; - } @Override @@ -126,7 +113,9 @@ public class TbDeviceStateNode implements TbNode { var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceConnectMsg(deviceConnectMsg) .build(); - ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + ctx.getClusterService().pushMsgToCore( + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ); } private void sendDeviceActivityMsg(TbContext ctx, TbMsg msg) { @@ -142,7 +131,9 @@ public class TbDeviceStateNode implements TbNode { var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceActivityMsg(deviceActivityMsg) .build(); - ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + ctx.getClusterService().pushMsgToCore( + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ); } private void sendDeviceDisconnectMsg(TbContext ctx, TbMsg msg) { @@ -157,7 +148,9 @@ public class TbDeviceStateNode implements TbNode { var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceDisconnectMsg(deviceDisconnectMsg) .build(); - ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + ctx.getClusterService().pushMsgToCore( + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ); } private void sendDeviceInactivityMsg(TbContext ctx, TbMsg msg) { @@ -172,7 +165,23 @@ public class TbDeviceStateNode implements TbNode { var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceInactivityMsg(deviceInactivityMsg) .build(); - ctx.getClusterService().pushMsgToCore(ctx.getTenantId(), msg.getOriginator(), toCoreMsg, EMPTY_CALLBACK); + ctx.getClusterService().pushMsgToCore( + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ); + } + + private TbQueueCallback getMsgProcessedCallback(TbContext ctx, TbMsg msg) { + return new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + ctx.tellSuccess(msg); + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }; } } From 6b5b654546fb5b49446c0fccc1eb53eff36bff91 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 12 Sep 2023 13:42:18 +0300 Subject: [PATCH 004/137] Fix grammar --- .../service/queue/DefaultTbCoreConsumerService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 02850e9d8c..8f3b0efc5b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -608,7 +608,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Tue, 19 Sep 2023 14:02:23 +0300 Subject: [PATCH 005/137] Add review fixes --- .../device/DeviceActorMessageProcessor.java | 4 +- .../actors/ruleChain/DefaultTbContext.java | 44 ++-- .../queue/DefaultTbCoreConsumerService.java | 14 +- .../state/DefaultDeviceStateService.java | 45 ++-- .../service/state/DeviceStateService.java | 6 +- .../state/DefaultDeviceStateServiceTest.java | 99 ++++---- common/cluster-api/src/main/proto/queue.proto | 3 + .../queue/common/SimpleTbQueueCallback.java | 47 ++++ .../rule/engine/action/TbDeviceStateNode.java | 78 +++--- .../engine/action/TbDeviceStateNodeTest.java | 238 ++++++------------ 10 files changed, 276 insertions(+), 302 deletions(-) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index b838f0b86d..55262cbf63 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -490,11 +490,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } private void reportSessionOpen() { - systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId); + systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId, System.currentTimeMillis()); } private void reportSessionClose() { - systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId); + systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId, System.currentTimeMillis()); } private void handleGetAttributesRequest(SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index ca1d174322..ed07456ed6 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -102,8 +102,7 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.common.SimpleTbQueueCallback; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine; @@ -209,7 +208,13 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "To Root Rule Chain"); } - mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure)); + mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, t -> { + if (onFailure != null) { + onFailure.accept(t); + } else { + log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); + } + })); } @Override @@ -295,7 +300,13 @@ class DefaultTbContext implements TbContext { relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, relationType, null, failureMessage)); } - mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure)); + mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, t -> { + if (onFailure != null) { + onFailure.accept(t); + } else { + log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); + } + })); } @Override @@ -923,29 +934,4 @@ class DefaultTbContext implements TbContext { return failureMessage; } - private class SimpleTbQueueCallback implements TbQueueCallback { - private final Runnable onSuccess; - private final Consumer onFailure; - - public SimpleTbQueueCallback(Runnable onSuccess, Consumer onFailure) { - this.onSuccess = onSuccess; - this.onFailure = onFailure; - } - - @Override - public void onSuccess(TbQueueMsgMetadata metadata) { - if (onSuccess != null) { - onSuccess.run(); - } - } - - @Override - public void onFailure(Throwable t) { - if (onFailure != null) { - onFailure.accept(t); - } else { - log.debug("[{}] Failed to put item into queue", nodeCtx.getTenantId(), t); - } - } - } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 8f3b0efc5b..7fb60f5da0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -605,10 +605,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) { updateActivityState(deviceId, stateData, lastReportedActivity); @@ -259,15 +259,14 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService kvEntries, String attributeName, long defaultValue) { if (kvEntries != null) { for (KvEntry entry : kvEntries) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java index b724b6e431..82c91d9baf 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java @@ -27,13 +27,13 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; */ public interface DeviceStateService extends ApplicationListener { - void onDeviceConnect(TenantId tenantId, DeviceId deviceId); + void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long lastConnectTime); void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivityTime); - void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId); + void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long lastDisconnectTime); - void onDeviceInactivity(TenantId tenantId, DeviceId deviceId); + void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long lastInactivityTime); void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout); diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 435fdbc523..6f99f3cfce 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -25,6 +25,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -54,11 +55,12 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -128,7 +130,7 @@ public class DefaultDeviceStateServiceTest { .willReturn(TopicPartitionInfo.builder().build()); // WHEN - service.onDeviceInactivity(tenantId, deviceId); + service.onDeviceInactivity(tenantId, deviceId, System.currentTimeMillis()); // THEN assertThat(service.deviceStates).isEmpty(); @@ -139,9 +141,13 @@ public class DefaultDeviceStateServiceTest { } @Test - public void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() throws InterruptedException { + public void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() { // GIVEN - initStateService(10000); + try { + initStateService(10000); + } catch (InterruptedException e) { + fail("Device state service failed to initialize!"); + } var deviceStateData = DeviceStateData.builder() .tenantId(tenantId) .deviceId(deviceId) @@ -150,33 +156,30 @@ public class DefaultDeviceStateServiceTest { .build(); service.deviceStates.put(deviceId, deviceStateData); + long lastInactivityTime = System.currentTimeMillis(); // WHEN - long timeBeforeCall = System.currentTimeMillis(); - service.onDeviceInactivity(tenantId, deviceId); - long timeAfterCall = System.currentTimeMillis(); + service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime); // THEN - var inactivityTimeCaptor = ArgumentCaptor.forClass(Long.class); - then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(INACTIVITY_ALARM_TIME), inactivityTimeCaptor.capture(), any() + then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any() ); - var actualInactivityTime = inactivityTimeCaptor.getValue(); - assertThat(actualInactivityTime).isGreaterThanOrEqualTo(timeBeforeCall); - assertThat(actualInactivityTime).isLessThanOrEqualTo(timeAfterCall); - - then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(false), any() + then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(ACTIVITY_STATE), eq(false), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); + then(clusterService).should(times(1)) + .pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); var actualMsg = msgCaptor.getValue(); assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); - then(notificationRuleProcessor).should().process(notificationCaptor.capture()); + then(notificationRuleProcessor).should(times(1)).process(notificationCaptor.capture()); var actualNotification = notificationCaptor.getValue(); assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); @@ -199,21 +202,24 @@ public class DefaultDeviceStateServiceTest { service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); // THEN - then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(INACTIVITY_ALARM_TIME), anyLong(), any() + then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(INACTIVITY_ALARM_TIME), anyLong(), any() ); - then(telemetrySubscriptionService).should().saveAttrAndNotify( - any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(false), any() + then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), + eq(ACTIVITY_STATE), eq(false), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); + then(clusterService).should(times(1)) + .pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); var actualMsg = msgCaptor.getValue(); assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); - then(notificationRuleProcessor).should().process(notificationCaptor.capture()); + then(notificationRuleProcessor).should(times(1)).process(notificationCaptor.capture()); var actualNotification = notificationCaptor.getValue(); assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); @@ -461,35 +467,38 @@ public class DefaultDeviceStateServiceTest { @Test public void givenConcurrentAccess_whenGetOrFetchDeviceStateData_thenFetchDeviceStateDataInvokedOnce() { - var deviceStateData = DeviceStateData.builder().build(); - var getOrFetchInvocationCounter = new AtomicInteger(); - doAnswer(invocation -> { - getOrFetchInvocationCounter.incrementAndGet(); Thread.sleep(100); - return deviceStateData; + return deviceStateDataMock; }).when(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); int numberOfThreads = 10; var allThreadsReadyLatch = new CountDownLatch(numberOfThreads); - var executor = Executors.newFixedThreadPool(numberOfThreads); - - for (int i = 0; i < numberOfThreads; i++) { - executor.submit(() -> { - allThreadsReadyLatch.countDown(); - try { - allThreadsReadyLatch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - service.getOrFetchDeviceStateData(deviceId); - }); - } - executor.shutdown(); - await().atMost(10, TimeUnit.SECONDS).until(executor::isTerminated); + ExecutorService executor = null; + try { + executor = Executors.newFixedThreadPool(numberOfThreads); + for (int i = 0; i < numberOfThreads; i++) { + executor.submit(() -> { + allThreadsReadyLatch.countDown(); + try { + allThreadsReadyLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + service.getOrFetchDeviceStateData(deviceId); + }); + } + + executor.shutdown(); + await().atMost(10, TimeUnit.SECONDS).until(executor::isTerminated); + } finally { + if (executor != null) { + executor.shutdownNow(); + } + } - assertThat(getOrFetchInvocationCounter.get()).isEqualTo(1); + then(service).should(times(1)).fetchDeviceStateDataUsingSeparateRequests(deviceId); } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 5260a4fa26..ef6a644c7f 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -485,6 +485,7 @@ message DeviceConnectProto { int64 tenantIdLSB = 2; int64 deviceIdMSB = 3; int64 deviceIdLSB = 4; + int64 lastConnectTime = 5; } message DeviceActivityProto { @@ -500,6 +501,7 @@ message DeviceDisconnectProto { int64 tenantIdLSB = 2; int64 deviceIdMSB = 3; int64 deviceIdLSB = 4; + int64 lastDisconnectTime = 5; } message DeviceInactivityProto { @@ -507,6 +509,7 @@ message DeviceInactivityProto { int64 tenantIdLSB = 2; int64 deviceIdMSB = 3; int64 deviceIdLSB = 4; + int64 lastInactivityTime = 5; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java new file mode 100644 index 0000000000..805511a603 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2023 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.queue.common; + +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; + +import java.util.function.Consumer; + +public class SimpleTbQueueCallback implements TbQueueCallback { + + private final Runnable onSuccess; + private final Consumer onFailure; + + public SimpleTbQueueCallback(Runnable onSuccess, Consumer onFailure) { + this.onSuccess = onSuccess; + this.onFailure = onFailure; + } + + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + if (onSuccess != null) { + onSuccess.run(); + } + } + + @Override + public void onFailure(Throwable t) { + if (onFailure != null) { + onFailure.accept(t); + } + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 8adafe8d62..37ecf34cdd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -28,9 +28,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.common.SimpleTbQueueCallback; -import java.util.Map; +import java.util.Set; @Slf4j @RuleNode( @@ -39,8 +39,9 @@ import java.util.Map; nodeDescription = "Triggers device connectivity events", nodeDetails = "If incoming message originator is a device," + " registers configured event for that device in the Device State Service," + - " which sends appropriate message to the Rule Engine. " + - "Incoming message is forwarded using the Success chain," + + " which sends appropriate message to the Rule Engine." + + " If metadata ts property is present, it will be used as event timestamp." + + " Incoming message is forwarded using the Success chain," + " unless an unexpected error occurs during message processing" + " then incoming message is forwarded using the Failure chain." + "
" + @@ -59,19 +60,10 @@ import java.util.Map; ) public class TbDeviceStateNode implements TbNode { - private final Map SUPPORTED_EVENTS = Map.of( - TbMsgType.CONNECT_EVENT, this::sendDeviceConnectMsg, - TbMsgType.ACTIVITY_EVENT, this::sendDeviceActivityMsg, - TbMsgType.DISCONNECT_EVENT, this::sendDeviceDisconnectMsg, - TbMsgType.INACTIVITY_EVENT, this::sendDeviceInactivityMsg + private static final Set SUPPORTED_EVENTS = Set.of( + TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); - private interface ConnectivityEvent { - - void sendEvent(TbContext ctx, TbMsg msg); - - } - private TbMsgType event; @Override @@ -80,7 +72,7 @@ public class TbDeviceStateNode implements TbNode { if (event == null) { throw new TbNodeException("Event cannot be null!", true); } - if (!SUPPORTED_EVENTS.containsKey(event)) { + if (!SUPPORTED_EVENTS.contains(event)) { throw new TbNodeException("Unsupported event: " + event, true); } this.event = event; @@ -90,15 +82,36 @@ public class TbDeviceStateNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { var originator = msg.getOriginator(); if (!ctx.isLocalEntity(originator)) { - log.warn("[{}][device-state-node] Received message from non-local entity [{}]!", ctx.getSelfId(), originator); + log.warn("[{}] Node [{}] received message from non-local entity [{}]!", + ctx.getTenantId().getId(), ctx.getSelfId().getId(), originator.getId()); + ctx.ack(msg); return; } if (!EntityType.DEVICE.equals(originator.getEntityType())) { ctx.tellSuccess(msg); return; } - SUPPORTED_EVENTS.get(event).sendEvent(ctx, msg); - ctx.tellSuccess(msg); + switch (event) { + case CONNECT_EVENT: { + sendDeviceConnectMsg(ctx, msg); + break; + } + case ACTIVITY_EVENT: { + sendDeviceActivityMsg(ctx, msg); + break; + } + case DISCONNECT_EVENT: { + sendDeviceDisconnectMsg(ctx, msg); + break; + } + case INACTIVITY_EVENT: { + sendDeviceInactivityMsg(ctx, msg); + break; + } + default: { + ctx.tellFailure(msg, new IllegalStateException("Configured event [" + event + "] is not supported!")); + } + } } private void sendDeviceConnectMsg(TbContext ctx, TbMsg msg) { @@ -109,12 +122,13 @@ public class TbDeviceStateNode implements TbNode { .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastConnectTime(msg.getMetaDataTs()) .build(); var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceConnectMsg(deviceConnectMsg) .build(); ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) ); } @@ -126,13 +140,13 @@ public class TbDeviceStateNode implements TbNode { .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastActivityTime(System.currentTimeMillis()) + .setLastActivityTime(msg.getMetaDataTs()) .build(); var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceActivityMsg(deviceActivityMsg) .build(); ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) ); } @@ -144,12 +158,13 @@ public class TbDeviceStateNode implements TbNode { .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastDisconnectTime(msg.getMetaDataTs()) .build(); var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceDisconnectMsg(deviceDisconnectMsg) .build(); ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) ); } @@ -161,27 +176,18 @@ public class TbDeviceStateNode implements TbNode { .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastInactivityTime(msg.getMetaDataTs()) .build(); var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() .setDeviceInactivityMsg(deviceInactivityMsg) .build(); ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgProcessedCallback(ctx, msg) + ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) ); } - private TbQueueCallback getMsgProcessedCallback(TbContext ctx, TbMsg msg) { - return new TbQueueCallback() { - @Override - public void onSuccess(TbQueueMsgMetadata metadata) { - ctx.tellSuccess(msg); - } - - @Override - public void onFailure(Throwable t) { - ctx.tellFailure(msg, t); - } - }; + private TbQueueCallback getMsgEnqueuedCallback(TbContext ctx, TbMsg msg) { + return new SimpleTbQueueCallback(() -> ctx.tellSuccess(msg), t -> ctx.tellFailure(msg, t)); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 123dffd0ea..176136244d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -15,10 +15,12 @@ */ package org.thingsboard.rule.engine.action; -import org.junit.jupiter.api.BeforeAll; 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.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -31,18 +33,20 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; import java.util.UUID; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -51,9 +55,6 @@ import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) public class TbDeviceStateNodeTest { - private static TenantId DUMMY_TENANT_ID; - private static TbMsg DUMMY_MSG; - private static DeviceId DUMMY_MSG_ORIGINATOR; @Mock private TbContext ctxMock; @Mock @@ -61,26 +62,26 @@ public class TbDeviceStateNodeTest { private TbDeviceStateNode node; private TbDeviceStateNodeConfiguration config; - @BeforeAll - public static void init() { - DUMMY_TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); + private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); + private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); + private static final long METADATA_TS = System.currentTimeMillis(); + private TbMsg msg; + @BeforeEach + public void setup() { var device = new Device(); - device.setTenantId(DUMMY_TENANT_ID); - device.setId(new DeviceId(UUID.randomUUID())); + device.setTenantId(TENANT_ID); + device.setId(DEVICE_ID); device.setName("My humidity sensor"); device.setType("Humidity sensor"); device.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID())); var metaData = new TbMsgMetaData(); metaData.putValue("deviceName", device.getName()); metaData.putValue("deviceType", device.getType()); - metaData.putValue("ts", String.valueOf(System.currentTimeMillis())); + metaData.putValue("ts", String.valueOf(METADATA_TS)); var data = JacksonUtil.newObjectNode(); data.put("humidity", 58.3); - DUMMY_MSG = TbMsg.newMsg( - TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), metaData, JacksonUtil.toString(data) - ); - DUMMY_MSG_ORIGINATOR = device.getId(); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), metaData, JacksonUtil.toString(data)); } @BeforeEach @@ -128,15 +129,15 @@ public class TbDeviceStateNodeTest { @Test public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { // GIVEN - var originator = new AssetId(UUID.randomUUID()); - var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, originator, new TbMsgMetaData(), "{}"); - given(ctxMock.isLocalEntity(originator)).willReturn(true); + var asset = new AssetId(UUID.randomUUID()); + var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, asset, new TbMsgMetaData(), "{}"); + given(ctxMock.isLocalEntity(asset)).willReturn(true); // WHEN node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should(times(1)).isLocalEntity(originator); + then(ctxMock).should(times(1)).isLocalEntity(asset); then(ctxMock).should(times(1)).tellSuccess(msg); then(ctxMock).shouldHaveNoMoreInteractions(); } @@ -144,178 +145,97 @@ public class TbDeviceStateNodeTest { @Test public void givenNonLocalOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { // GIVEN - given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(false); + given(ctxMock.isLocalEntity(msg.getOriginator())).willReturn(false); + given(ctxMock.getSelfId()).willReturn(new RuleNodeId(UUID.randomUUID())); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); // WHEN - node.onMsg(ctxMock, DUMMY_MSG); + node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should(times(1)).isLocalEntity(DUMMY_MSG_ORIGINATOR); + then(ctxMock).should(times(1)).isLocalEntity(DEVICE_ID); + then(ctxMock).should().getTenantId(); then(ctxMock).should().getSelfId(); + then(ctxMock).should(times(1)).ack(msg); then(ctxMock).shouldHaveNoMoreInteractions(); } - @Test - public void givenConnectEventInConfig_whenOnMsg_thenOnDeviceConnectCalledAndTellsSuccess() { + @ParameterizedTest + @MethodSource("provideSupportedEventsAndExpectedMessages") + public void givenSupportedEvent_whenOnMsg_thenCorrectMsgIsSent( + TbMsgType event, TransportProtos.ToCoreMsg expectedToCoreMsg + ) { // GIVEN - config.setEvent(TbMsgType.CONNECT_EVENT); + config.setEvent(event); var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); try { node.init(ctxMock, nodeConfig); } catch (TbNodeException e) { fail("Node failed to initialize!", e); } - given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); - given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); + given(ctxMock.isLocalEntity(msg.getOriginator())).willReturn(true); // WHEN - node.onMsg(ctxMock, DUMMY_MSG); - - // THEN - var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); - then(tbClusterServiceMock).should(times(1)) - .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); - - TransportProtos.DeviceConnectProto expectedDeviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() - .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) - .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) - .build(); - TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceConnectMsg(expectedDeviceConnectMsg) - .build(); - assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); - - then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); - then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); - then(ctxMock).shouldHaveNoMoreInteractions(); - } - - @Test - public void givenActivityEventInConfig_whenOnMsg_thenOnDeviceActivityCalledWithCorrectTimeAndTellsSuccess() - throws TbNodeException { - // GIVEN - config.setEvent(TbMsgType.ACTIVITY_EVENT); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctxMock, nodeConfig); - given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); - given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); - given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); - - - // WHEN - long timeBeforeCall = System.currentTimeMillis(); - node.onMsg(ctxMock, DUMMY_MSG); - long timeAfterCall = System.currentTimeMillis(); + node.onMsg(ctxMock, msg); // THEN var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); + var callbackCaptor = ArgumentCaptor.forClass(TbQueueCallback.class); then(tbClusterServiceMock).should(times(1)) - .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); - - TransportProtos.ToCoreMsg actualToCoreMsg = protoCaptor.getValue(); - long actualLastActivityTime = actualToCoreMsg.getDeviceActivityMsg().getLastActivityTime(); - - assertThat(actualLastActivityTime).isGreaterThanOrEqualTo(timeBeforeCall); - assertThat(actualLastActivityTime).isLessThanOrEqualTo(timeAfterCall); - - TransportProtos.DeviceActivityProto updatedActivityMsg = actualToCoreMsg.getDeviceActivityMsg().toBuilder() - .setLastActivityTime(123L) - .build(); - TransportProtos.ToCoreMsg updatedToCoreMsg = actualToCoreMsg.toBuilder() - .setDeviceActivityMsg(updatedActivityMsg) - .build(); + .pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), protoCaptor.capture(), callbackCaptor.capture()); - TransportProtos.DeviceActivityProto expectedDeviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) - .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) - .setLastActivityTime(123L) - .build(); - TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceActivityMsg(expectedDeviceActivityMsg) - .build(); + TbQueueCallback actualCallback = callbackCaptor.getValue(); - assertThat(updatedToCoreMsg).isEqualTo(expectedToCoreMsg); - - then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); - then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); - then(ctxMock).shouldHaveNoMoreInteractions(); - } - - @Test - public void givenInactivityEventInConfig_whenOnMsg_thenOnDeviceInactivityCalledAndTellsSuccess() - throws TbNodeException { - // GIVEN - config.setEvent(TbMsgType.INACTIVITY_EVENT); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctxMock, nodeConfig); - given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); - given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); - given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); - - // WHEN - node.onMsg(ctxMock, DUMMY_MSG); + actualCallback.onSuccess(null); + then(ctxMock).should(times(1)).tellSuccess(msg); - // THEN - var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); - then(tbClusterServiceMock).should(times(1)) - .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); + var throwable = new Throwable(); + actualCallback.onFailure(throwable); + then(ctxMock).should(times(1)).tellFailure(msg, throwable); - TransportProtos.DeviceInactivityProto expectedDeviceInactivityMsg = - TransportProtos.DeviceInactivityProto.newBuilder() - .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) - .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) - .build(); - TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceInactivityMsg(expectedDeviceInactivityMsg) - .build(); assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); - then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); then(ctxMock).shouldHaveNoMoreInteractions(); } - @Test - public void givenDisconnectEventInConfig_whenOnMsg_thenOnDeviceDisconnectCalledAndTellsSuccess() - throws TbNodeException { - // GIVEN - config.setEvent(TbMsgType.DISCONNECT_EVENT); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - node.init(ctxMock, nodeConfig); - given(ctxMock.getTenantId()).willReturn(DUMMY_TENANT_ID); - given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); - given(ctxMock.isLocalEntity(DUMMY_MSG.getOriginator())).willReturn(true); - - // WHEN - node.onMsg(ctxMock, DUMMY_MSG); - - // THEN - var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); - then(tbClusterServiceMock).should(times(1)) - .pushMsgToCore(eq(DUMMY_TENANT_ID), eq(DUMMY_MSG_ORIGINATOR), protoCaptor.capture(), any()); - - TransportProtos.DeviceDisconnectProto expectedDeviceDisconnectMsg = - TransportProtos.DeviceDisconnectProto.newBuilder() - .setTenantIdMSB(DUMMY_TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(DUMMY_TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DUMMY_MSG_ORIGINATOR.getId().getMostSignificantBits()) - .setDeviceIdLSB(DUMMY_MSG_ORIGINATOR.getId().getLeastSignificantBits()) - .build(); - TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceDisconnectMsg(expectedDeviceDisconnectMsg) - .build(); - assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); - - then(ctxMock).should(times(1)).tellSuccess(DUMMY_MSG); - then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); - then(ctxMock).shouldHaveNoMoreInteractions(); + private static Stream provideSupportedEventsAndExpectedMessages() { + return Stream.of( + Arguments.of(TbMsgType.CONNECT_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceConnectMsg( + TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastConnectTime(METADATA_TS) + .build()).build()), + Arguments.of(TbMsgType.ACTIVITY_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg( + TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastActivityTime(METADATA_TS) + .build()).build()), + Arguments.of(TbMsgType.DISCONNECT_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceDisconnectMsg( + TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastDisconnectTime(METADATA_TS) + .build()).build()), + Arguments.of(TbMsgType.INACTIVITY_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceInactivityMsg( + TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastInactivityTime(METADATA_TS) + .build()).build()) + ); } } From 1303975c05f7bde0b64d85140f2e0dc61ef602c5 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 19 Sep 2023 18:17:46 +0300 Subject: [PATCH 006/137] Revert removal of runtime exception --- .../server/service/queue/DefaultTbCoreConsumerService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 7fb60f5da0..b4344c1cd6 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -624,7 +624,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Wed, 20 Sep 2023 14:38:37 +0300 Subject: [PATCH 007/137] Add mock test for new and a part of exisiting logic in core consumer; make changes according to review comments --- .../queue/DefaultTbCoreConsumerService.java | 8 +- .../state/DefaultDeviceStateService.java | 2 +- .../DefaultTbCoreConsumerServiceTest.java | 281 ++++++++++++++++++ .../state/DefaultDeviceStateServiceTest.java | 18 +- .../rule/engine/action/TbDeviceStateNode.java | 2 +- .../engine/action/TbDeviceStateNodeTest.java | 5 + 6 files changed, 297 insertions(+), 19 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index b4344c1cd6..8dbe9254eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -601,7 +601,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService SUPPORTED_EVENTS = Set.of( + static final Set SUPPORTED_EVENTS = Set.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 176136244d..cb81b4d2e4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; +import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -97,6 +98,10 @@ public class TbDeviceStateNodeTest { // THEN assertThat(config.getEvent()).isEqualTo(TbMsgType.ACTIVITY_EVENT); + assertThat(TbDeviceStateNode.SUPPORTED_EVENTS).isEqualTo(Set.of( + TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, + TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT + )); } @Test From 0cfed35d5c0917d782fa49177c4cee93d651d7e4 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 20 Sep 2023 15:05:55 +0300 Subject: [PATCH 008/137] Fix licenses --- .../service/state/DefaultDeviceStateService.java | 8 ++++---- .../service/state/DefaultDeviceStateServiceTest.java | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 10aea4d326..a141db1807 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2023 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 - *

+ * + * 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. diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 600ab62ace..3913316e6e 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2023 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 - *

+ * + * 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. @@ -194,6 +194,8 @@ public class DefaultDeviceStateServiceTest { .metaData(new TbMsgMetaData()) .build(); + given(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).willReturn(tpi); + // WHEN service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); From 45f3448305baa8d862907e43e522fb9d36a76fcf Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 20 Sep 2023 15:15:11 +0300 Subject: [PATCH 009/137] Fix formatting --- .../state/DefaultDeviceStateService.java | 3 +- .../state/DefaultDeviceStateServiceTest.java | 58 +++++++++---------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index a141db1807..7e07a6534a 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -156,8 +156,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService provideParametersForUpdateInactivityStateIfExpired() { return Stream.of( - Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false), + Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false), - Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false), + Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false), - Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false), + Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false), - Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false), + Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false), - Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false), + Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false), - Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false), + Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false), - Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false), + Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false), - Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false), + Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false), - Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true), + Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true), - Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false), + Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false), - Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true), + Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true), - Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true), + Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true), - Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true), + Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true), - Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false), + Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false), - Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true), + Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true), - Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true), + Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true), - Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false), + Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false), - Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false), + Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false), - Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false), + Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false), - Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true), + Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true), - Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false), + Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false), - Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false), + Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false), - Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false), + Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false), - Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true), + Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true), - Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true), + Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true), - Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false), + Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false), - Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true), + Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true), - Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false), + Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false), - Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false) + Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false) ); } From d0cd4252d13b59cd20f792ccc2d1c2f693288a30 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 3 Oct 2023 18:13:50 +0300 Subject: [PATCH 010/137] Add EnumSet usage --- .../org/thingsboard/rule/engine/action/TbDeviceStateNode.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 65787b3f61..7a4def297f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -30,6 +30,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.SimpleTbQueueCallback; +import java.util.EnumSet; import java.util.Set; @Slf4j @@ -60,7 +61,7 @@ import java.util.Set; ) public class TbDeviceStateNode implements TbNode { - static final Set SUPPORTED_EVENTS = Set.of( + static final Set SUPPORTED_EVENTS = EnumSet.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); From 1e001dda0a3155e544d01fa4790e2278537aefee Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 24 Oct 2023 15:15:39 +0300 Subject: [PATCH 011/137] Change log in case non-local originator --- .../org/thingsboard/rule/engine/action/TbDeviceStateNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 7a4def297f..c3593d6cc7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -83,7 +83,7 @@ public class TbDeviceStateNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { var originator = msg.getOriginator(); if (!ctx.isLocalEntity(originator)) { - log.warn("[{}] Node [{}] received message from non-local entity [{}]!", + log.warn("[{}][{}] Received message from non-local entity [{}]!", ctx.getTenantId().getId(), ctx.getSelfId().getId(), originator.getId()); ctx.ack(msg); return; From f80ba069ad3c2f80b5b5c9899194bebe26a06886 Mon Sep 17 00:00:00 2001 From: kalytka Date: Mon, 4 Dec 2023 10:31:21 +0200 Subject: [PATCH 012/137] share module --- ui-ngx/src/app/modules/common/modules-map.ts | 24 ++++++++++++++++++- .../home/components/home-components.module.ts | 14 ++++++++--- .../app/modules/home/components/public-api.ts | 2 ++ .../basic/basic-widget-config.module.ts | 11 --------- .../config/widget-config-components.module.ts | 13 ++++++++-- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index d93a84ea51..76294e6330 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -188,7 +188,13 @@ import * as MaterialIconsComponent from '@shared/components/material-icons.compo import * as TbIconComponent from '@shared/components/icon.component'; import * as HintTooltipIconComponent from '@shared/components/hint-tooltip-icon.component'; +import * as CssUnitSelectComponent from '@home/components/widget/lib/settings/common/css-unit-select.component'; +import * as WidgetActionsPanelComponent from '@home/components/widget/config/basic/common/widget-actions-panel.component'; +import * as FontSettingsComponent from '@home/components/widget/lib/settings/common/font-settings.component'; +import * as ColorSettingsComponent from '@home/components/widget/lib/settings/common/color-settings.component'; import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component'; +import * as DatasourceComponent from '@home/components/widget/config/datasources.component'; +import * as DataKeysPanelComponent from '@home/components/widget/config/basic/common/data-keys-panel.component'; import * as EntitiesTableComponent from '@home/components/entity/entities-table.component'; import * as DetailsPanelComponent from '@home/components/details-panel.component'; import * as EntityDetailsPanelComponent from '@home/components/entity/entity-details-panel.component'; @@ -313,6 +319,11 @@ import * as AssetProfileAutocompleteComponent from '@home/components/profile/ass import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-chain-select.component'; import { IModulesMap } from '@modules/common/modules-map.models'; +import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; +import { AlarmAssigneePanelComponent } from '@home/components/alarm/alarm-assignee-panel.component'; +import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; +import { AlarmFilterConfigComponent } from '@home/components/alarm/alarm-filter-config.component'; declare const System; @@ -495,7 +506,14 @@ class ModulesMap implements IModulesMap { '@shared/components/icon.component': TbIconComponent, '@shared/components/hint-tooltip-icon.component': HintTooltipIconComponent, + '@home/components/alarm/alarm-filter-config.component': AlarmFilterConfigComponent, + '@home/components/alarm/alarm-comment-dialog.component': AlarmCommentDialogComponent, + '@home/components/alarm/alarm-assignee-panel.component': AlarmAssigneePanelComponent, + '@home/components/alarm/alarm-details-dialog.component': AlarmDetailsDialogComponent, + '@home/components/widget/lib/display-columns-panel.component': DisplayColumnsPanelComponent, '@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent, + '@home/components/widget/config/datasources.component': DatasourceComponent, + '@home/components/widget/config/basic/common/data-keys-panel.component': DataKeysPanelComponent, '@home/components/entity/entities-table.component': EntitiesTableComponent, '@home/components/details-panel.component': DetailsPanelComponent, '@home/components/entity/entity-details-panel.component': EntityDetailsPanelComponent, @@ -617,7 +635,11 @@ class ModulesMap implements IModulesMap { '@home/components/dashboard-page/dashboard-image-dialog.component': DashboardImageDialogComponent, '@home/components/widget/widget-container.component': WidgetContainerComponent, '@home/components/profile/queue/tenant-profile-queues.component': TenantProfileQueuesComponent, - '@home/components/queue/queue-form.component': QueueFormComponent + '@home/components/queue/queue-form.component': QueueFormComponent, + '@home/components/widget/lib/settings/common/color-settings.component': ColorSettingsComponent, + '@home/components/widget/lib/settings/common/font-settings.component': FontSettingsComponent, + '@home/components/widget/config/basic/common/widget-actions-panel.component': WidgetActionsPanelComponent, + '@home/components/widget/lib/settings/common/css-unit-select.component': CssUnitSelectComponent }; init() { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 46e9f15275..e60dadf626 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -182,10 +182,13 @@ import { ExportWidgetsBundleDialogComponent } from '@home/components/import-export/export-widgets-bundle-dialog.component'; import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component'; +import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; +import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; @NgModule({ declarations: [ + ScrollGridComponent, RouterTabsComponent, EntitiesTableComponent, AddEntityDialogComponent, @@ -326,8 +329,7 @@ import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component RateLimitsComponent, RateLimitsTextComponent, RateLimitsDetailsDialogComponent, - SendNotificationButtonComponent, - ScrollGridComponent + SendNotificationButtonComponent ], imports: [ CommonModule, @@ -339,9 +341,15 @@ import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component SnmpDeviceProfileTransportModule, StatesControllerModule, DeviceCredentialsModule, - DeviceProfileCommonModule + DeviceProfileCommonModule, + WidgetSettingsCommonModule, + WidgetComponentsModule ], exports: [ + WidgetComponentsModule, + SharedHomeComponentsModule, + WidgetSettingsCommonModule, + WidgetConfigComponentsModule, RouterTabsComponent, EntitiesTableComponent, AddEntityDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/public-api.ts b/ui-ngx/src/app/modules/home/components/public-api.ts index aca4d33e39..22748a828c 100644 --- a/ui-ngx/src/app/modules/home/components/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/public-api.ts @@ -17,3 +17,5 @@ export * from './home-components.module'; export * from './widget/config/widget-config.component.models'; +export * from './widget/lib/table-widget.models'; +export * from './widget/lib/flot-widget.models'; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 5bb6d9ea52..4b41a8ad09 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -22,14 +22,9 @@ import { WidgetConfigComponentsModule } from '@home/components/widget/config/wid import { SimpleCardBasicConfigComponent } from '@home/components/widget/config/basic/cards/simple-card-basic-config.component'; -import { - WidgetActionsPanelComponent -} from '@home/components/widget/config/basic/common/widget-actions-panel.component'; import { EntitiesTableBasicConfigComponent } from '@home/components/widget/config/basic/entity/entities-table-basic-config.component'; -import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component'; -import { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component'; import { TimeseriesTableBasicConfigComponent } from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component'; @@ -88,7 +83,6 @@ import { @NgModule({ declarations: [ - WidgetActionsPanelComponent, SimpleCardBasicConfigComponent, EntitiesTableBasicConfigComponent, TimeseriesTableBasicConfigComponent, @@ -98,8 +92,6 @@ import { AggregatedValueCardBasicConfigComponent, AggregatedDataKeyRowComponent, AggregatedDataKeysPanelComponent, - DataKeyRowComponent, - DataKeysPanelComponent, AlarmCountBasicConfigComponent, EntityCountBasicConfigComponent, BatteryLevelBasicConfigComponent, @@ -119,7 +111,6 @@ import { WidgetConfigComponentsModule ], exports: [ - WidgetActionsPanelComponent, SimpleCardBasicConfigComponent, EntitiesTableBasicConfigComponent, TimeseriesTableBasicConfigComponent, @@ -129,8 +120,6 @@ import { AggregatedValueCardBasicConfigComponent, AggregatedDataKeyRowComponent, AggregatedDataKeysPanelComponent, - DataKeyRowComponent, - DataKeysPanelComponent, AlarmCountBasicConfigComponent, EntityCountBasicConfigComponent, BatteryLevelBasicConfigComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts index ea0d27dcf6..86b1a7457c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts @@ -32,6 +32,9 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; import { TimewindowStyleComponent } from '@home/components/widget/config/timewindow-style.component'; import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; +import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component'; +import { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component'; +import { WidgetActionsPanelComponent } from '@home/components/widget/config/basic/common/widget-actions-panel.component'; @NgModule({ declarations: @@ -48,7 +51,10 @@ import { TimewindowStylePanelComponent } from '@home/components/widget/config/ti TimewindowStyleComponent, TimewindowStylePanelComponent, TimewindowConfigPanelComponent, - WidgetSettingsComponent + DataKeysPanelComponent, + DataKeyRowComponent, + WidgetSettingsComponent, + WidgetActionsPanelComponent ], imports: [ CommonModule, @@ -61,6 +67,8 @@ import { TimewindowStylePanelComponent } from '@home/components/widget/config/ti AlarmFilterConfigComponent, DataKeysComponent, DataKeyConfigDialogComponent, + DataKeysPanelComponent, + DataKeyRowComponent, DataKeyConfigComponent, DatasourceComponent, DatasourcesComponent, @@ -70,7 +78,8 @@ import { TimewindowStylePanelComponent } from '@home/components/widget/config/ti TimewindowStylePanelComponent, TimewindowConfigPanelComponent, WidgetSettingsComponent, - WidgetSettingsCommonModule + WidgetSettingsCommonModule, + WidgetActionsPanelComponent ] }) export class WidgetConfigComponentsModule { } From 7263a769e9ac67c6a5202a8cea68138fcc93058a Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 8 Dec 2023 08:47:34 +0100 Subject: [PATCH 013/137] Update locale.constant-de_DE.json Wrong commas --- .../assets/locale/locale.constant-de_DE.json | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 7545c37c47..29fc2d10c4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -34,7 +34,7 @@ "unassign": "Zuordnung aufheben", "share": "Teilen", "make-private": "Privat machen", - "make-public": "Öffentlich machen", + "make-public": "Öffentlich machen", "apply": "Anwenden", "apply-changes": "Änderungen übernehmen", "edit-mode": "Bearbeitungsmodus", @@ -67,10 +67,10 @@ "next-with-label": "Nächste: {{label}}", "read-more": "Mehr dazu", "hide": "Verstecken", - "done": "Erledigt", - "print": "Drucken", - "restore": "Wiederherstellen", - "confirm": "Bestätigen" + "done": "Erledigt", + "print": "Drucken", + "restore": "Wiederherstellen", + "confirm": "Bestätigen", "more": "Mehr", "less": "Weniger", "skip": "Überspringen", @@ -107,6 +107,23 @@ "base-url-required": "Basis-URL ist erforderlich.", "prohibit-different-url": "Prohibit to use hostname from the client request headers", "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", + "device-connectivity": { + "device-connectivity": "Geräte Konnektivität", + "http-s": "HTTP(s)", + "mqtt-s": "MQTT(s)", + "coap-s": "COAP(s)", + "http": "HTTP", + "https": "HTTPs", + "mqtt": "MQTT", + "mqtts": "MQTTs", + "coap": "COAP", + "coaps": "COAPs", + "hint": "Falls Host und Port leer sind, werden die Standardwerte des Protokolls verwendet", + "host": "Host", + "port": "Port", + "port-pattern": "Port muss einen positiven Wert haben", + "port-range": "Port sollte im Bereich 1 und 65535 liegen." + }, "mail-from": "E-Mail von", "mail-from-required": "E-Mail von ist erforderlich.", "smtp-protocol": "SMTP Protokoll", @@ -139,6 +156,15 @@ "test": "E-Mail Nachricht testen", "activation": "Nachricht Benutzerkontoaktivierung", "account-activated": "Nachricht Benutzerkonto aktiviert", + "account-lockout": "Nachricht zur Kontosperrung", + "reset-password": "Nachricht zum Zurücksetzen des Passworts", + "password-was-reset": "Nachricht für Passwort wurde zurückgesetzt", + "user-activated": "Nachricht für Benutzerkonto wurde aktiviert", + "user-registered": "Nachricht für Benutzer hat sich registriert", + "api-usage-state-enabled": "API-Nutzungsstatus wurde aktiviert", + "api-usage-state-warning": "Warnung zum API-Nutzungsstatus", + "api-usage-state-disabled": "API-Nutzungsstatus wurde deaktiviert", + "two-fa-verification": "2FA-Bestätigungsnachricht" }, "mail-subject": "E-Mail Betreff", "mail-body": "E-Mail Nachricht", @@ -186,17 +212,17 @@ "alarm-status-list": "Alarm Statusliste", "any-status": "Jeder Status", "search-status": { - "ANY": "Jeder", - "ACTIVE": "Aktiv", - "CLEARED": "Gelöscht", - "ACK": "Bestätigt", - "UNACK": "Nicht bestätigt" + "ANY": "Jeder", + "ACTIVE": "Aktiv", + "CLEARED": "Gelöscht", + "ACK": "Bestätigt", + "UNACK": "Nicht bestätigt" }, "display-status": { - "ACTIVE_UNACK": "Nicht bestätigt aktiv", - "ACTIVE_ACK": "Bestätigt aktiv", - "CLEARED_UNACK": "Nicht bestätigt", - "CLEARED_ACK": "Bestätigung gelöscht" + "ACTIVE_UNACK": "Nicht bestätigt aktiv", + "ACTIVE_ACK": "Bestätigt aktiv", + "CLEARED_UNACK": "Nicht bestätigt", + "CLEARED_ACK": "Bestätigung gelöscht" }, "no-alarms-prompt": "Keine Alarme gefunden", "created-time": "Erstellungszeit", @@ -270,8 +296,8 @@ "filter-type-edge-type-description": "Rand vom Typ '{{edgeTypes}}'", "filter-type-relations-query": "Beziehungsabfrage", "filter-type-relations-query-description": "{{entities}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", - "filter-type-edge-search-query": "Edge-Abfrage", - "filter-type-edge-search-query-description": "Edge vom Typ {{edgeTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "filter-type-edge-search-query": "Edge-Abfrage", + "filter-type-edge-search-query-description": "Edge vom Typ {{edgeTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "filter-type-asset-search-query": "Objektabfrage", "filter-type-asset-search-query-description": "Objekte vom Typ {{assetTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", "filter-type-device-search-query": "Geräteabfrage", @@ -404,12 +430,12 @@ "add-to-dashboard": "Zum Dashboard hinzufügen", "add-widget-to-dashboard": "Widget zum Dashboard hinzufügen", "selected-attributes": "{ count, plural, =1 {1 Eigenschaft} other {# Eigenschaften} } ausgewählt", - "selected-telemetry": "{ count, plural, =1 {1 Telemetrieeinheit } other {# Telemetrieeinheiten} } ausgewählt" + "selected-telemetry": "{ count, plural, =1 {1 Telemetrieeinheit } other {# Telemetrieeinheiten} } ausgewählt", "no-attributes-text": "Keine Attribute gefunden", "no-telemetry-text": "Keine Telemetriedaten gefunden", "copy-key": "Schlüssel kopieren", "add-telemetry": "Telemetriedaten hinzufügen", - "copy-value": "Wert kopieren", + "copy-value": "Wert kopieren" }, "audit-log": { "audit": "Audit", @@ -1677,7 +1703,7 @@ "hours": "Stunden", "minutes": "Minuten", "seconds": "Sekunden", - "advanced": "Erweitert" + "advanced": "Erweitert", "predefined": { "yesterday": "Gestern", "day-before-yesterday": "Vorgestern", From ef1a826893e3e84c2d9dc1d8975f5bfb4922dc0a Mon Sep 17 00:00:00 2001 From: fulai Date: Tue, 2 Jan 2024 18:46:26 +0800 Subject: [PATCH 014/137] Update locale.constant-zh_CN.json --- .../assets/locale/locale.constant-zh_CN.json | 148 ++++++++++++++++-- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index f0d07fa506..c2394b7656 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -77,7 +77,10 @@ "show-more": "显示更多", "dont-show-again": "不再显示", "see-documentation": "查看文档", - "clear": "清除" + "clear": "清除", + "upload": "上传", + "delete-anyway": "仍要删除", + "delete-selected": "删除所选" }, "aggregation": { "aggregation": "聚合", @@ -470,7 +473,12 @@ "notifications-settings": "通知设置", "slack-api-token": "Slack API令牌", "slack": "Slack", - "slack-settings": "Slack 设置" + "slack-settings": "Slack 设置", + "maximum-password-length": "密码最大长度", + "maximum-password-length-min": "密码最大长度应至少为6个字符", + "maximum-password-length-less-min": "密码最大长度应大于最小长度", + "force-reset-password-if-no-valid": "如果密码不可用则强制重置密码", + "force-reset-password-if-no-valid-hint": "启用此功能时请小心:它会要求使用不可用密码的用户通过电子邮件重置其密码。" }, "alarm": { "alarm": "告警", @@ -1188,7 +1196,9 @@ "assign-dashboard-to-edge": "将仪表板分配给边缘", "assign-dashboard-to-edge-text": "请选择要分配给边缘的仪表板", "non-existent-dashboard-state-error": "找不到ID为 '{{ stateId }}' 的仪表板状态。", - "edit-mode": "编辑模式" + "edit-mode": "编辑模式", + "state-controller-default": "静态(已弃用)", + "duplicate-state-action": "复制状态" }, "datakey": { "settings": "设置", @@ -2017,7 +2027,8 @@ "sync-process-started-successfully": "同步处理开始成功!", "missing-related-rule-chains-title": "边缘缺少关联规则链", "missing-related-rule-chains-text": "分配给边缘的规则链使用规则节点将消息转发给未分配给当前边缘的规则链。

缺少的规则链列表:
{{missingRuleChains}}", - "widget-datasource-error": "组件只支持边缘实体数据源" + "widget-datasource-error": "组件只支持边缘实体数据源", + "upgrade-instructions": "升级说明" }, "edge-event": { "type-dashboard": "仪表板", @@ -2211,7 +2222,9 @@ "type-notification-request": "通知请求", "type-notification-template": "通知模板", "type-notification-templates": "通知模板", - "list-of-notification-templates": "{ count, plural, =1 {1 个通知模板} other {# 个通知模板} }" + "list-of-notification-templates": "{ count, plural, =1 {1 个通知模板} other {# 个通知模板} }", + "type-tb-resources": "资源", + "list-of-tb-resources": "{ count, plural, =1 {1 个资源} other {# 个资源} }" }, "entity-field": { "created-time": "创建时间", @@ -2908,7 +2921,13 @@ "grpc-max-pings-without-data": "在没有接收到任何数据之前,服务器可以发送的keepalive ping消息的最大数量,然后将连接视为死亡", "grpc-min-ping-interval-without-data": "在没有发送或接收数据时,服务器在发送keepalive ping消息之间应等待的最小时间量", "permit-without-calls": "允许服务器在没有活动RPC调用时保持GRPC连接活动" - } + }, + "docker-label": "使用以下指令在 Docker compose 中运行 IoT 网关,并为选定的设备提供凭据", + "install-docker-compose": "使用以下说明下载、安装和设置 Docker Compose", + "download-configuration-file": "下载配置文件", + "download-docker-compose": "下载您的网关的 docker-compose.yml 文件", + "launch-gateway": "启动网关", + "launch-docker-compose": "在包含 docker-compose.yml 文件的文件夹中,使用以下命令在终端中启动网关" }, "grid": { "delete-item-title": "确定要删除此项吗?", @@ -2939,6 +2958,59 @@ "browse-file": "浏览文件", "browse-files": "浏览文件" }, + "image": { + "gallery": "图像库", + "search": "搜索图像", + "selected-images": "{count, plural, =1 {已选择 1 个图像} other {已选择 # 个图像}}", + "created-time": "创建时间", + "name": "名称", + "name-required": "名称不能为空。", + "resolution": "分辨率", + "size": "大小", + "system": "系统", + "download-image": "下载图像", + "export-image": "导出图像为JSON", + "import-image": "从JSON导入图像", + "upload-image": "上传图像", + "edit-image": "编辑图像", + "image-details": "图像详情", + "no-images": "未找到图像", + "delete-image": "删除图像", + "delete-image-title": "确定要删除图像“{{imageTitle}}”吗?", + "delete-image-text": "请注意,确认后图像将无法恢复。", + "delete-images-title": "确定要删除{count, plural, =1 {1 个图像} other {# 个图像}}吗?", + "delete-images-text": "请注意,确认后所有选定的图像都将被删除,并且所有相关数据将无法恢复。", + "list-mode": "列表视图", + "grid-mode": "网格视图", + "image-preview": "图像预览", + "update-image": "更新图像", + "export-failed-error": "无法导出图像:{{error}}", + "image-json-file": "图像JSON文件", + "invalid-image-json-file-error": "无法从JSON导入图像:无效的图像JSON数据结构。", + "image-is-in-use": "图像被其他实体使用", + "images-are-in-use": "图像被其他实体使用", + "image-is-in-use-text": "无法删除图像'{{title}}',因为它被以下实体使用:", + "images-are-in-use-text": "由于图像被其他实体使用,无法删除所有图像。
您可以通过单击相应图像行上的引用按钮查看引用的实体。
如果仍然要删除这些图像,请在下方的表格中选择它们,然后点击删除所选按钮。", + "delete-image-in-use-text": "如果仍然要删除该图像,请点击无论如何删除按钮。", + "system-entities": "系统实体:", + "entities": "实体:", + "references": "引用", + "include-system-images": "包含系统图像", + "clear-image": "清除图像", + "no-image": "无图像", + "no-image-selected": "未选择图像", + "browse-from-gallery": "从图像库浏览", + "set-link": "设置链接", + "image-link": "图像链接", + "link": "链接", + "copy-image-link": "复制图像链接", + "embed-image": "嵌入图像", + "embed-to-html": "嵌入到HTML", + "embed-to-html-hint": "此功能将使链接对任何未经授权的用户可用。", + "embed-to-html-text": "使用以下代码片段,您可以将图像嵌入到基于纯HTML的组件中。
此类组件包括HTML卡片小部件、单元格内容函数等。", + "embed-to-angular-template": "嵌入到Angular HTML模板", + "embed-to-angular-template-text": "使用以下代码片段,您可以将图像嵌入到Angular HTML模板中。
此类组件包括Markdown小部件、小部件编辑器中的HTML部分、自定义操作等。" + }, "image-input": { "drop-images-or": "拖放一张或多张图片", "drag-and-drop": "拖放", @@ -3190,8 +3262,8 @@ "email-preview": "Email 通知预览", "slack": "Slack", "slack-preview": "Slack 通知预览", - "microsoft-teams" : "Microsoft Teams", - "microsoft-teams-preview" : "Microsoft Teams 通知预览", + "microsoft-teams": "Microsoft Teams", + "microsoft-teams-preview": "Microsoft Teams 通知预览", "sms": "SMS", "sms-preview": "SMS 通知预览", "web": "Web", @@ -3457,7 +3529,8 @@ "version-tag": "版本标签", "version-tag-hint": "自定义标签应与您设备报告的软件包版本相匹配。", "version-max-length": "版本长度应该少于256个字符", - "warning-after-save-no-edit": "上传包后,您将无法修改标题、版本、设备配置和包类型。" + "warning-after-save-no-edit": "上传包后,您将无法修改标题、版本、设备配置和包类型。", + "package-file": "包文件" }, "position": { "top": "顶部", @@ -3555,7 +3628,8 @@ "password-requirements": "密码要求", "password-should-difference": "新密码应与当前密码不同", "special-character": "{ count, plural, =1 {1 位特殊字符} other {# 位特殊字符} }", - "uppercase-letter": "{ count, plural, =1 {1 位大写字母} other {# 位大写字母} }" + "uppercase-letter": "{ count, plural, =1 {1 位大写字母} other {# 位大写字母} }", + "at-most": "最多:" } }, "relation": { @@ -3638,7 +3712,9 @@ "js-module": "JS 模块", "lwm2m-model": "LWM2M 模型", "pkcs-12": "PKCS #12" - } + }, + "resource-file": "资源文件", + "resource-files": "资源文件" }, "rulechain": { "rulechain": "规则链", @@ -4102,7 +4178,10 @@ "transport-device-telemetry-msg": "传输设备遥测消息", "transport-device-telemetry-data-points": "传输设备遥测数据点", "sec": "秒" - } + }, + "maximum-resource-size": "最大资源文件大小(字节)", + "maximum-resource-size-required": "最大资源文件大小是必需的", + "maximum-resource-size-range": "最大资源文件大小不能为负数" }, "timeinterval": { "seconds-interval": "{ seconds, plural, =1 {1 秒} other {# 秒} }", @@ -4598,7 +4677,24 @@ "deg-per-second": "deg/s", "degrees-brix": "°Bx", "katal": "kat", - "katal-per-cubic-metre": "kat/m^3" + "katal-per-cubic-metre": "kat/m^3", + "milliampere-hour-tags": "mAh", + "ampere-hours-tags": "Ah", + "kiloampere-hours-tags": "kAh", + "nanoampere-tags": "nA", + "picoampere-tags": "pA", + "microampere-tags": "μA", + "milliampere-tags": "mA", + "ampere-tags": "A", + "kiloamperes-tags": "kA", + "microampere-per-square-centimeter-tags": "µA/cm²", + "ampere-per-square-meter-tags": "A/m²", + "ampere-per-meter-tags": "A/m", + "oersted-tags": "Oe", + "bohr-magneton-tags": "μB", + "ampere-meter-squared-tags": "A·m²", + "ampere-meter-tags": "A·m", + "milligram-per-cubic-meter": "mg/m³" }, "user": { "user": "用户", @@ -6600,6 +6696,30 @@ "items-per-page-separator": "of" }, "language": { - "language": "语言" + "language": "语言", + "locales": { + "ca_ES": "Catalan", + "cs_CZ": "Česky", + "da_DK": "Dansk", + "de_DE": "Deutsch", + "el_GR": "Ελληνικά", + "en_US": "English", + "es_ES": "Español", + "fa_IR": "فارسي", + "fr_FR": "Français", + "it_IT": "Italiano", + "ja_JP": "日本語", + "ka_GE": "ქართული", + "ko_KR": "한국어", + "lv_LV": "Latviešu", + "nl_BE": "Koninkrijk België", + "pt_BR": "Português do Brasil", + "ro_RO": "Română", + "sl_SI": "Slovenščina", + "tr_TR": "Türkçe", + "uk_UA": "Українська", + "zh_CN": "简体中文", + "zh_TW": "繁體中文" + } } } From 3c0b4b2029ec0d8ad4524c0acda2aad68d51c083 Mon Sep 17 00:00:00 2001 From: fulai Date: Wed, 3 Jan 2024 11:33:07 +0800 Subject: [PATCH 015/137] Update locale.constant-zh_CN.json --- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index c2394b7656..1939b54ec2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -2961,7 +2961,7 @@ "image": { "gallery": "图像库", "search": "搜索图像", - "selected-images": "{count, plural, =1 {已选择 1 个图像} other {已选择 # 个图像}}", + "selected-images": "已选择 { count, plural, =1 {1 个图像} other {# 个图像} }", "created-time": "创建时间", "name": "名称", "name-required": "名称不能为空。", @@ -2976,9 +2976,9 @@ "image-details": "图像详情", "no-images": "未找到图像", "delete-image": "删除图像", - "delete-image-title": "确定要删除图像“{{imageTitle}}”吗?", + "delete-image-title": "确定要删除图像 '{{imageTitle}}' 吗?", "delete-image-text": "请注意,确认后图像将无法恢复。", - "delete-images-title": "确定要删除{count, plural, =1 {1 个图像} other {# 个图像}}吗?", + "delete-images-title": "确定要删除 { count, plural, =1 {1 个图像} other {# 个图像} } 吗?", "delete-images-text": "请注意,确认后所有选定的图像都将被删除,并且所有相关数据将无法恢复。", "list-mode": "列表视图", "grid-mode": "网格视图", From 518db972f6dbbd6fb4a4cab56d39f73fbadcc6fc Mon Sep 17 00:00:00 2001 From: kalytka Date: Fri, 5 Jan 2024 13:21:16 +0200 Subject: [PATCH 016/137] update widget-component.module --- .../modules/home/components/widget/widget-components.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 858856fb4a..eac2f715c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -117,6 +117,7 @@ import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/dough ], exports: [ EntitiesTableWidgetComponent, + SelectEntityDialogComponent, AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent, EntitiesHierarchyWidgetComponent, From d3dad03a9732fc4cd600bccbe3ff2d4d427d83b0 Mon Sep 17 00:00:00 2001 From: kalytka Date: Tue, 9 Jan 2024 15:52:49 +0200 Subject: [PATCH 017/137] Refactoring --- ui-ngx/src/app/modules/common/modules-map.ts | 24 +++++++++---------- .../home/components/home-components.module.ts | 7 +++--- .../basic/basic-widget-config.module.ts | 11 +++++++++ .../config/widget-config-components.module.ts | 13 ++-------- .../widget/widget-components.module.ts | 1 - 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index fbbced965c..b19a588d8b 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -196,9 +196,14 @@ import * as CssUnitSelectComponent from '@home/components/widget/lib/settings/co import * as WidgetActionsPanelComponent from '@home/components/widget/config/basic/common/widget-actions-panel.component'; import * as FontSettingsComponent from '@home/components/widget/lib/settings/common/font-settings.component'; import * as ColorSettingsComponent from '@home/components/widget/lib/settings/common/color-settings.component'; -import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component'; +import * as DisplayColumnsPanelComponent from '@home/components/widget/lib/display-columns-panel.component'; +import * as AlarmDetailsDialogComponent from '@home/components/alarm/alarm-details-dialog.component'; +import * as AlarmAssigneePanelComponent from '@home/components/alarm/alarm-assignee-panel.component'; +import * as AlarmCommentDialogComponent from '@home/components/alarm/alarm-comment-dialog.component'; +import * as AlarmFilterConfigComponent from '@home/components/alarm/alarm-filter-config.component'; import * as DatasourceComponent from '@home/components/widget/config/datasources.component'; import * as DataKeysPanelComponent from '@home/components/widget/config/basic/common/data-keys-panel.component'; +import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component'; import * as EntitiesTableComponent from '@home/components/entity/entities-table.component'; import * as DetailsPanelComponent from '@home/components/details-panel.component'; import * as EntityDetailsPanelComponent from '@home/components/entity/entity-details-panel.component'; @@ -323,11 +328,6 @@ import * as AssetProfileAutocompleteComponent from '@home/components/profile/ass import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-chain-select.component'; import { IModulesMap } from '@modules/common/modules-map.models'; -import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; -import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; -import { AlarmAssigneePanelComponent } from '@home/components/alarm/alarm-assignee-panel.component'; -import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; -import { AlarmFilterConfigComponent } from '@home/components/alarm/alarm-filter-config.component'; declare const System; @@ -523,9 +523,13 @@ class ModulesMap implements IModulesMap { '@home/components/alarm/alarm-assignee-panel.component': AlarmAssigneePanelComponent, '@home/components/alarm/alarm-details-dialog.component': AlarmDetailsDialogComponent, '@home/components/widget/lib/display-columns-panel.component': DisplayColumnsPanelComponent, - '@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent, '@home/components/widget/config/datasources.component': DatasourceComponent, '@home/components/widget/config/basic/common/data-keys-panel.component': DataKeysPanelComponent, + '@home/components/widget/lib/settings/common/color-settings.component': ColorSettingsComponent, + '@home/components/widget/lib/settings/common/font-settings.component': FontSettingsComponent, + '@home/components/widget/config/basic/common/widget-actions-panel.component': WidgetActionsPanelComponent, + '@home/components/widget/lib/settings/common/css-unit-select.component': CssUnitSelectComponent, + '@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent, '@home/components/entity/entities-table.component': EntitiesTableComponent, '@home/components/details-panel.component': DetailsPanelComponent, '@home/components/entity/entity-details-panel.component': EntityDetailsPanelComponent, @@ -644,11 +648,7 @@ class ModulesMap implements IModulesMap { '@home/components/dashboard-page/dashboard-image-dialog.component': DashboardImageDialogComponent, '@home/components/widget/widget-container.component': WidgetContainerComponent, '@home/components/profile/queue/tenant-profile-queues.component': TenantProfileQueuesComponent, - '@home/components/queue/queue-form.component': QueueFormComponent, - '@home/components/widget/lib/settings/common/color-settings.component': ColorSettingsComponent, - '@home/components/widget/lib/settings/common/font-settings.component': FontSettingsComponent, - '@home/components/widget/config/basic/common/widget-actions-panel.component': WidgetActionsPanelComponent, - '@home/components/widget/lib/settings/common/css-unit-select.component': CssUnitSelectComponent + '@home/components/queue/queue-form.component': QueueFormComponent }; init() { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index f9b50082eb..5d1b2d12f0 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -323,14 +323,14 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component SharedModule, SharedHomeComponentsModule, WidgetConfigComponentsModule, - BasicWidgetConfigModule, Lwm2mProfileComponentsModule, SnmpDeviceProfileTransportModule, StatesControllerModule, DeviceCredentialsModule, DeviceProfileCommonModule, WidgetSettingsCommonModule, - WidgetComponentsModule + WidgetComponentsModule, + BasicWidgetConfigModule ], exports: [ WidgetComponentsModule, @@ -456,7 +456,8 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component RateLimitsComponent, RateLimitsTextComponent, RateLimitsDetailsDialogComponent, - SendNotificationButtonComponent + SendNotificationButtonComponent, + BasicWidgetConfigModule ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index 1490d60d42..5e95d162cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -22,9 +22,14 @@ import { WidgetConfigComponentsModule } from '@home/components/widget/config/wid import { SimpleCardBasicConfigComponent } from '@home/components/widget/config/basic/cards/simple-card-basic-config.component'; +import { + WidgetActionsPanelComponent +} from '@home/components/widget/config/basic/common/widget-actions-panel.component'; import { EntitiesTableBasicConfigComponent } from '@home/components/widget/config/basic/entity/entities-table-basic-config.component'; +import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component'; +import { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component'; import { TimeseriesTableBasicConfigComponent } from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component'; @@ -86,6 +91,7 @@ import { @NgModule({ declarations: [ + WidgetActionsPanelComponent, SimpleCardBasicConfigComponent, EntitiesTableBasicConfigComponent, TimeseriesTableBasicConfigComponent, @@ -95,6 +101,8 @@ import { AggregatedValueCardBasicConfigComponent, AggregatedDataKeyRowComponent, AggregatedDataKeysPanelComponent, + DataKeyRowComponent, + DataKeysPanelComponent, AlarmCountBasicConfigComponent, EntityCountBasicConfigComponent, BatteryLevelBasicConfigComponent, @@ -115,6 +123,7 @@ import { WidgetConfigComponentsModule ], exports: [ + WidgetActionsPanelComponent, SimpleCardBasicConfigComponent, EntitiesTableBasicConfigComponent, TimeseriesTableBasicConfigComponent, @@ -124,6 +133,8 @@ import { AggregatedValueCardBasicConfigComponent, AggregatedDataKeyRowComponent, AggregatedDataKeysPanelComponent, + DataKeyRowComponent, + DataKeysPanelComponent, AlarmCountBasicConfigComponent, EntityCountBasicConfigComponent, BatteryLevelBasicConfigComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts index 86b1a7457c..ea0d27dcf6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts @@ -32,9 +32,6 @@ import { TimewindowConfigPanelComponent } from '@home/components/widget/config/t import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; import { TimewindowStyleComponent } from '@home/components/widget/config/timewindow-style.component'; import { TimewindowStylePanelComponent } from '@home/components/widget/config/timewindow-style-panel.component'; -import { DataKeysPanelComponent } from '@home/components/widget/config/basic/common/data-keys-panel.component'; -import { DataKeyRowComponent } from '@home/components/widget/config/basic/common/data-key-row.component'; -import { WidgetActionsPanelComponent } from '@home/components/widget/config/basic/common/widget-actions-panel.component'; @NgModule({ declarations: @@ -51,10 +48,7 @@ import { WidgetActionsPanelComponent } from '@home/components/widget/config/basi TimewindowStyleComponent, TimewindowStylePanelComponent, TimewindowConfigPanelComponent, - DataKeysPanelComponent, - DataKeyRowComponent, - WidgetSettingsComponent, - WidgetActionsPanelComponent + WidgetSettingsComponent ], imports: [ CommonModule, @@ -67,8 +61,6 @@ import { WidgetActionsPanelComponent } from '@home/components/widget/config/basi AlarmFilterConfigComponent, DataKeysComponent, DataKeyConfigDialogComponent, - DataKeysPanelComponent, - DataKeyRowComponent, DataKeyConfigComponent, DatasourceComponent, DatasourcesComponent, @@ -78,8 +70,7 @@ import { WidgetActionsPanelComponent } from '@home/components/widget/config/basi TimewindowStylePanelComponent, TimewindowConfigPanelComponent, WidgetSettingsComponent, - WidgetSettingsCommonModule, - WidgetActionsPanelComponent + WidgetSettingsCommonModule ] }) export class WidgetConfigComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 640f1ba970..6679ed0cca 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -119,7 +119,6 @@ import { RangeChartWidgetComponent } from '@home/components/widget/lib/chart/ran ], exports: [ EntitiesTableWidgetComponent, - SelectEntityDialogComponent, AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent, EntitiesHierarchyWidgetComponent, From 48446d04b39d4f3bea38d215ad1ae7a016af599a Mon Sep 17 00:00:00 2001 From: fulai Date: Wed, 10 Jan 2024 10:04:50 +0800 Subject: [PATCH 018/137] Update locale.constant-zh_CN.json --- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 1939b54ec2..b030bba759 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -4853,7 +4853,7 @@ "no-widgets-text": "未找到部件", "management": "管理部件", "editor": "部件编辑器", - "confirm-to-exit-editor-html": "You have unsaved widget settings.
Are you sure you want to leave this page?", + "confirm-to-exit-editor-html": "有未保存的部件设置。
确定要离开此页面吗?", "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。", "widget-type-load-error": "由于以下错误未加载部件:", "remove": "删除部件", From 9f342ba76f2461790685a78209dbd54587a50165 Mon Sep 17 00:00:00 2001 From: fulai Date: Wed, 10 Jan 2024 10:36:25 +0800 Subject: [PATCH 019/137] Update locale.constant-zh_CN.json --- .../assets/locale/locale.constant-zh_CN.json | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b030bba759..ea560710c8 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1212,13 +1212,13 @@ "data-generation-func": "数据生成功能", "use-data-post-processing-func": "使用数据后处理功能", "configuration": "数据键配置", - "timeseries": "Timeseries", + "timeseries": "时间序列", "attributes": "属性", "entity-field": "实体字段", "alarm": "告警字段", - "timeseries-required": "实体 Timeseries 必填。", - "timeseries-or-attributes-required": "实体 Timeseries/属性必填。", - "alarm-fields-timeseries-or-attributes-required": "告警字段或实体 Timeseries/属性必填。", + "timeseries-required": "实体时间序列必填。", + "timeseries-or-attributes-required": "实体时间序列/属性必填。", + "alarm-fields-timeseries-or-attributes-required": "告警字段或实体时间序列/属性必填。", "maximum-timeseries-or-attributes": "最多允许 { count, plural, =1 {1 个 timeseries/属性。} other {# 个 timeseries/属性。} }", "alarm-fields-required": "告警字段必填。", "function-types": "函数类型", @@ -1236,10 +1236,10 @@ "latest-key": "最新值数据键", "latest-key-functions": "最新值数据键函数", "latest-key-function": "最新值数据键函数", - "timeseries-keys": "Timeseries 数据键", - "timeseries-key": "Timeseries 数据键", - "timeseries-key-functions": "Timeseries数据键函数", - "timeseries-key-function": "Timeseries数据键函数", + "timeseries-keys": "时间序列数据键", + "timeseries-key": "时间序列数据键", + "timeseries-key-functions": "时间序列数据键函数", + "timeseries-key-function": "时间序列数据键函数", "maximum-function-types": "最多允许 { count, plural, =1 {1 个函数类型} other {# 个函数类型} }", "time-description": "当前值的时间戳;", "value-description": "当前值;", @@ -1588,9 +1588,9 @@ "mqtt-send-ack-on-validation-exception": "发布消息验证失败时发送PUBACK", "mqtt-send-ack-on-validation-exception-hint": "默认情况下平台将关闭相关消息验证失败的MQTT会话,启用后平台将发布确认而不是关闭会话。", "snmp-add-mapping": "添加SNMP映射", - "snmp-mapping-not-configured": "OID到Timeseries/遥测的映射未配置", - "snmp-timseries-or-attribute-name": "用于映射的Timeseries/属性名称", - "snmp-timseries-or-attribute-type": "用于映射的Timeseries/属性类型", + "snmp-mapping-not-configured": "OID到时间序列/遥测的映射未配置", + "snmp-timseries-or-attribute-name": "用于映射的时间序列/属性名称", + "snmp-timseries-or-attribute-type": "用于映射的时间序列/属性类型", "snmp-method-pdu-type-get-request": "GetRequest", "snmp-method-pdu-type-get-next-request": "GetNextRequest", "snmp-oid": "OID", @@ -2057,7 +2057,7 @@ "action-type-post-attributes": "推送属性", "action-type-attributes-updated": "属性更新", "action-type-attributes-deleted": "属性删除", - "action-type-timeseries-updated": "时序更新", + "action-type-timeseries-updated": "时间序列更新", "action-type-credentials-updated": "认证更新", "action-type-assigned-to-customer": "分配给客户", "action-type-unassigned-from-customer": "取消分配客户", @@ -2341,7 +2341,7 @@ "attributes-propagation": "属性传播", "attributes-propagation-hint": "每次保存或更新这个实体视图时,实体视图将自动从目标实体复制指定的属性。由于性能原因,目标实体属性不会在每次属性更改时传递到实体视图。您可以通过配置\"copy to view\"规则链中的规则节点,并将\"Post attributes\"和\"attributes Updated\"消息链接到新规则节点,从而启用自动传递。", "timeseries-data": "时间序列数据", - "timeseries-data-hint": "配置目标实体的 Timeseries 数据键,以便实体视图可以访问这些键。此 Timeseries 数据是只读的。", + "timeseries-data-hint": "配置目标实体的时间序列数据键,以便实体视图可以访问这些键。此时间序列数据是只读的。", "search": "查找实体视图", "selected-entity-views": "已选择 { count, plural, =1 {1 个实体视图} other {# 个实体视图} }", "make-public-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为公开吗?", @@ -2440,8 +2440,8 @@ "attributes": "属性", "add-attribute": "添加属性", "add-map": "添加映射元素", - "timeseries": "Timeseries", - "add-timeseries": "添加 Timeseries", + "timeseries": "时间序列", + "add-timeseries": "添加时间序列", "field-required": "必填字段", "brokers": "代理服务器组", "add-broker": "添加代理服务器", @@ -2536,7 +2536,7 @@ "modbus-device-name": "设备名称", "modbus-poll-period": "轮询周期 (毫秒)", "modbus-attributes-poll-period": "轮询属性周期 (毫秒)", - "modbus-timeseries-poll-period": "Timeseries 轮询周期 (毫秒)", + "modbus-timeseries-poll-period": "时间序列数据轮询周期 (毫秒)", "modbus-poll-period-range": "轮询周期应为正值。", "modbus-tag": "标签", "modbus-function": "函数", @@ -2626,7 +2626,7 @@ "key-type": { "key-type": "键类型", "attribute": "属性", - "timeseries": "Timeseries", + "timeseries": "时间序列", "entity-field": "实体", "constant": "常量", "client-attribute": "客户端属性", @@ -3044,7 +3044,7 @@ "client-attribute": "客户端属性", "shared-attribute": "共享属性", "server-attribute": "服务器属性", - "timeseries": "Timeseries", + "timeseries": "时间序列", "entity-field": "实体字段", "access-token": "访问令牌", "x509": "X.509", @@ -4857,18 +4857,18 @@ "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。", "widget-type-load-error": "由于以下错误未加载部件:", "remove": "删除部件", - "delete": "Delete widget", + "delete": "删除部件", "edit": "编辑部件", "remove-widget-title": "确定要删除 '{{widgetTitle}}'部件吗?", "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", - "timeseries": "Timeseries", + "timeseries": "时间序列", "search-data": "查找数据", "no-data-found": "未找到数据", "latest": "最新值", "rpc": "控件部件", "alarm": "告警部件", "static": "静态部件", - "timeseries-short": "时序", + "timeseries-short": "时间序列", "latest-short": "最新", "rpc-short": "控制", "alarm-short": "告警", @@ -5239,12 +5239,12 @@ "axis-position-top": "顶部 (默认)", "axis-position-bottom": "底部", "custom-legend-settings": "自定义图例设置", - "enable-custom-legend": "启用自定义图例 (这将允许您在键标签中使用属性/Timeseries值)", + "enable-custom-legend": "启用自定义图例 (这将允许您在键标签中使用属性/时间序列值)", "key-name": "键名", "key-name-required": "键名是必需的", "key-type": "键类型", "key-type-attribute": "属性", - "key-type-timeseries": "Timeseries", + "key-type-timeseries": "时间序列", "label-keys-list": "要在标签中使用的键列表", "no-label-keys": "未配置键", "add-label-key": "添加新键", @@ -5653,7 +5653,7 @@ "discard-changes": "放弃更改", "entity-attribute-required": "实体属性必填", "entity-coordinate-required": "纬度和经度两个字段都是必需的", - "entity-timeseries-required": "实体 Timeseries 必填", + "entity-timeseries-required": "实体时间序列必填", "get-location": "获取当前位置", "invalid-date": "无效日期", "latitude": "纬度", @@ -5671,19 +5671,19 @@ "enable-https-use-widget": "请启用HTTPS以使用此部件", "no-found-your-camera": "未找到摄像机", "no-permission-camera": "权限被用户拒绝/此站点无权使用摄像机", - "no-timeseries-selected": "未选择 Timeseries", + "no-timeseries-selected": "未选择时间序列值", "secret-key": "密钥", "secret-key-required": "密钥必填", "switch-attribute-value": "切换实体属性值", "switch-camera": "切换摄像机", - "switch-timeseries-value": "切换实体 Timeseries 值", + "switch-timeseries-value": "切换实体时间序列值", "take-photo": "拍照", "time": "时间", - "timeseries-not-allowed": "Timeseries 参数不能用于此部件", + "timeseries-not-allowed": "时间序列参数不能用于此部件", "update-failed": "更新失败", "update-successful": "更新成功", "update-attribute": "更新属性", - "update-timeseries": "更新 Timeseries", + "update-timeseries": "更新时间序列", "value": "数值", "general-settings": "通用设置", "widget-title": "部件标题", @@ -5739,7 +5739,7 @@ "attribute-settings": "属性设置", "widget-mode": "部件模式", "widget-mode-update-attribute": "更新属性", - "widget-mode-update-timeseries": "更新 Timeseries", + "widget-mode-update-timeseries": "更新时间序列", "attribute-scope": "属性范围", "attribute-scope-server": "服务器属性", "attribute-scope-shared": "共享属性", @@ -5770,7 +5770,7 @@ "datakey-type": "数据键类型", "datakey-type-server": "服务器属性(默认)", "datakey-type-shared": "共享属性", - "datakey-type-timeseries": "Timeseries", + "datakey-type-timeseries": "时间序列", "datakey-value-type": "数据键值类型", "datakey-value-type-string": "字符串", "datakey-value-type-double": "双精度", @@ -5934,7 +5934,7 @@ "retrieve-value-method-attribute": "订阅属性获取值", "retrieve-value-method-timeseries": "订阅时间序列获取值", "attribute-value-key": "属性键", - "timeseries-value-key": "Timeseries键", + "timeseries-value-key": "时间序列键", "get-value-method": "RPC获取值方法", "parse-value-function": "解析值的函数", "update-value-settings": "更新值设置", From 9d6b487a7f41dee987d10207a329151c00324256 Mon Sep 17 00:00:00 2001 From: fulai Date: Wed, 10 Jan 2024 10:38:59 +0800 Subject: [PATCH 020/137] Update locale.constant-zh_CN.json --- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index ea560710c8..161df5362c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -4868,7 +4868,7 @@ "rpc": "控件部件", "alarm": "告警部件", "static": "静态部件", - "timeseries-short": "时间序列", + "timeseries-short": "时序", "latest-short": "最新", "rpc-short": "控制", "alarm-short": "告警", From 84926b92a39f0ed8bc4a54aacf140e49aef4456a Mon Sep 17 00:00:00 2001 From: fulai Date: Wed, 10 Jan 2024 14:27:24 +0800 Subject: [PATCH 021/137] Update locale.constant-zh_CN.json --- ui-ngx/src/assets/locale/locale.constant-zh_CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 161df5362c..996fe3140e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -606,7 +606,7 @@ "filter-type-single-entity": "单个实体", "filter-type-entity-list": "实体列表", "filter-type-entity-name": "实体名称", - "filter-type-entity-type": "Entity type", + "filter-type-entity-type": "实体类型", "filter-type-state-entity": "仪表板状态实体", "filter-type-state-entity-description": "仪表板实体令牌状态参数", "filter-type-asset-type": "资产类型", From f27ce0686efa83a201a070d23d9ab1a02ec3fc4b Mon Sep 17 00:00:00 2001 From: artem Date: Wed, 10 Jan 2024 14:02:54 +0200 Subject: [PATCH 022/137] TbGpsGeofencingActionNode: added presenceMonitoringStrategyOnEachMessage + tests + node details --- .../engine/geo/TbGpsGeofencingActionNode.java | 39 ++- ...bGpsGeofencingActionNodeConfiguration.java | 3 + .../rule/engine/util/GpsGeofencingEvents.java | 23 ++ .../geo/TbGpsGeofencingActionNodeTest.java | 264 ++++++++++++++++++ 4 files changed, 325 insertions(+), 4 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java index abdcc5b299..cd88a1eda7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java @@ -15,6 +15,8 @@ */ package org.thingsboard.rule.engine.geo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -28,6 +30,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import java.util.Collections; @@ -39,6 +42,11 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; + /** * Created by ashvayka on 19.01.18. */ @@ -46,15 +54,22 @@ import java.util.concurrent.TimeoutException; @RuleNode( type = ComponentType.ACTION, name = "gps geofencing events", + version = 1, configClazz = TbGpsGeofencingActionNodeConfiguration.class, relationTypes = {"Success", "Entered", "Left", "Inside", "Outside"}, nodeDescription = "Produces incoming messages using GPS based geofencing", - nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters", + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters. " + + "

" + + "If an object with coordinates extracted from incoming message enters the geofence, sends a message with the type Entered. " + + "If an object leaves the geofence, sends a message with the type Left. " + + "If the presence monitoring strategy \"On first message\" is selected, sends messages with types Inside or Outside only the first time the geofencing and duration conditions are satisfied; otherwise Success. " + + "If the presence monitoring strategy \"On each message\" is selected, sends messages with types Inside or Outside every time the geofencing condition is satisfied.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGpsGeofencingConfig" ) public class TbGpsGeofencingActionNode extends AbstractGeofencingNode { + private static final String PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE = "presenceMonitoringStrategyOnEachMessage"; private final Map entityStates = new HashMap<>(); private final Gson gson = new Gson(); private final JsonParser parser = new JsonParser(); @@ -83,18 +98,21 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode (entityState.isInside() ? TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { setStaid(ctx, msg.getOriginator(), entityState); - ctx.tellNext(msg, entityState.isInside() ? "Inside" : "Outside"); + ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); told = true; } } + } else { + ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); + told = true; } if (!told) { ctx.tellSuccess(msg); @@ -127,4 +145,17 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode getConfigClazz() { return TbGpsGeofencingActionNodeConfiguration.class; } + + @Override + public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + boolean hasChanges = false; + if (fromVersion == 0) { + if (!oldConfiguration.has(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).put(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE, false); + } + } + return new TbPair<>(hasChanges, oldConfiguration); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java index d0adad8996..ee375083b4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java @@ -31,6 +31,8 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte private String minInsideDurationTimeUnit; private String minOutsideDurationTimeUnit; + private boolean presenceMonitoringStrategyOnEachMessage; + @Override public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration(); @@ -43,6 +45,7 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte configuration.setMinOutsideDurationTimeUnit(TimeUnit.MINUTES.name()); configuration.setMinInsideDuration(1); configuration.setMinOutsideDuration(1); + configuration.setPresenceMonitoringStrategyOnEachMessage(false); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java new file mode 100644 index 0000000000..db5ddaf698 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/GpsGeofencingEvents.java @@ -0,0 +1,23 @@ +/** + * 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.util; + +public class GpsGeofencingEvents { + public static final String ENTERED = "Entered"; + public static final String INSIDE = "Inside"; + public static final String LEFT = "Left"; + public static final String OUTSIDE = "Outside"; +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java new file mode 100644 index 0000000000..dd7536f4ee --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -0,0 +1,264 @@ +/** + * 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.geo; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE; +import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; + +class TbGpsGeofencingActionNodeTest { + private TbContext ctx; + private TbGpsGeofencingActionNode node; + private AttributesService attributesService; + + @BeforeEach + void setUp() { + ctx = mock(TbContext.class); + attributesService = mock(AttributesService.class); + node = new TbGpsGeofencingActionNode(); + } + + @AfterEach + void tearDown() { + node.destroy(); + } + + private static Stream givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes() { + return Stream.of( + // default config with presenceMonitoringStrategyOnEachMessage false + Arguments.of(false, List.of( + Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 1), + Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 1), + Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 2) + )), + // default config with presenceMonitoringStrategyOnEachMessage true + Arguments.of(true, List.of( + Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 2, SUCCESS, 0), + Map.of(ENTERED, 1, INSIDE, 3, SUCCESS, 0) + )) + ); + } + + @ParameterizedTest + @MethodSource + void givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes( + boolean presenceMonitoringStrategyOnEachMessage, + List> outputMsgTypesCountList + ) throws TbNodeException { + // GIVEN + var config = new TbGpsGeofencingActionNodeConfiguration().defaultConfiguration(); + config.setPresenceMonitoringStrategyOnEachMessage(presenceMonitoringStrategyOnEachMessage); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); + TbMsg msg = getTbMsg(deviceId, metadata, + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + + when(ctx.getAttributesService()).thenReturn(attributesService); + when(ctx + .getAttributesService() + .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getServiceId())) + .thenReturn(Futures.immediateFuture(Optional.empty())); + + // WHEN + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + node.onMsg(ctx, msg); + + // THEN + verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(0)); + + // WHEN + msg = getTbMsg(deviceId, metadata, + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); + node.onMsg(ctx, msg); + + // THEN + verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(1)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(2)); + + // WHEN + config.setMinInsideDuration(0); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + node.onMsg(ctx, msg); + + // THEN + verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(3)); + + // WHEN + node.onMsg(ctx, msg); + + // THEN + verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(4)); + } + + private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { + String data = "{\"latitude\": " + latitude + ", \"longitude\": " + longitude + "}"; + return TbMsg.newMsg(TbMsgType.POST_ATTRIBUTES_REQUEST, entityId, metadata, data); + } + + private TbMsgMetaData getMetadataForNewVersionPolygonPerimeter() { + var metadata = new TbMsgMetaData(); + metadata.putValue("ss_perimeter", GeoUtilTest.SIMPLE_RECT); + return metadata; + } + + private void verifyNodeOutputs(ArgumentCaptor newMsgCaptor, Map outputMsgTypesCount) { + verify(this.ctx, times(outputMsgTypesCount.get(ENTERED))).tellNext(newMsgCaptor.capture(), eq(ENTERED)); + verify(this.ctx, times(outputMsgTypesCount.get(INSIDE))).tellNext(newMsgCaptor.capture(), eq(INSIDE)); + verify(this.ctx, times(outputMsgTypesCount.get(SUCCESS))).tellSuccess(newMsgCaptor.capture()); + } + + // Rule nodes upgrade + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { + return Stream.of( + // default config for version 0 + Arguments.of(0, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n", + true, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n"), + // default config for version 1 with upgrade from version 0 + Arguments.of(0, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n", + false, + "{\n" + + " \"minInsideDuration\": 1,\n" + + " \"minOutsideDuration\": 1,\n" + + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + + " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"latitudeKeyName\": \"latitude\",\n" + + " \"longitudeKeyName\": \"longitude\",\n" + + " \"perimeterType\": \"POLYGON\",\n" + + " \"fetchPerimeterInfoFromMessageMetadata\": true,\n" + + " \"perimeterKeyName\": \"ss_perimeter\",\n" + + " \"polygonsDefinition\": null,\n" + + " \"centerLatitude\": null,\n" + + " \"centerLongitude\": null,\n" + + " \"range\": null,\n" + + " \"rangeUnit\": null\n" + + "}\n") + ); + } + + @ParameterizedTest + @MethodSource + void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { + // GIVEN + JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); + JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); + + // WHEN + TbPair upgradeResult = node.upgrade(givenVersion, givenConfig); + + // THEN + assertThat(upgradeResult.getFirst()).isEqualTo(hasChanges); + ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); + assertThat(upgradedConfig).isEqualTo(expectedConfig); + } + +} From 95f30d2be705f8ab0b9e2027fa0000da46a02956 Mon Sep 17 00:00:00 2001 From: fulai Date: Tue, 16 Jan 2024 14:21:40 +0800 Subject: [PATCH 023/137] Update locale.constant-zh_CN.json --- .../assets/locale/locale.constant-zh_CN.json | 836 +++++++++--------- 1 file changed, 418 insertions(+), 418 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 996fe3140e..10db674ea3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -4277,424 +4277,424 @@ "preview": "预览" }, "unit": { - "millimeter": "mm", - "centimeter": "cm", - "angstrom": "Å", - "nanometer": "nm", - "micrometer": "μm", - "meter": "m", - "kilometer": "km", - "inch": "in", - "foot": "ft", - "yard": "yd", - "mile": "mi", - "nautical-mile": "nmi", - "astronomical-unit": "au", - "reciprocal-metre": "m⁻¹", - "meter-per-meter": "m/m", - "steradian": "sr", - "thou": "th", - "barleycorn": "bc", - "hand": "hd", - "chain": "ch", - "furlong": "fur", - "league": "lea", - "fathom": "fath", - "cable": "cb", - "link": "li", - "rod": "rd", - "nanogram": "ng", - "microgram": "μg", - "milligram": "mg", - "gram": "g", - "kilogram": "kg", - "tonne": "t", - "ounce": "oz", - "pound": "lb", - "stone": "st", - "hundredweight-count": "cwt", - "short-tons": "s.t.", - "dalton": "Da", - "grain": "gr", - "drachm": "dr", - "quarter": "qr", - "slug": "slug", - "carat": "ct", - "cubic-millimeter": "mm³", - "cubic-centimeter": "cm³", - "cubic-meter": "m³", - "cubic-kilometer": "km³", - "microliter": "μL", - "milliliter": "mL", - "liter": "L", - "hectoliter": "hL", - "cubic-inch": "in³", - "cubic-foot": "ft³", - "cubic-yard": "yd³", - "fluid-ounce": "fl oz", - "pint": "pt", - "quart": "qt", - "gallon": "gal", - "oil-barrels": "bbl", - "cubic-meter-per-kilogram": "m³/kg", - "gill": "gi", - "hogshead": "hhd", - "teaspoon": "tsp", - "tablespoon": "tbsp", - "cup": "cup", - "celsius": "°C", - "kelvin": "K", - "rankine": "°R", - "fahrenheit": "°F", - "percent": "%", - "meter-per-second": "m/s", - "kilometer-per-hour": "km/h", - "foot-per-second": "ft/s", - "mile-per-hour": "mph", - "knot": "kn", - "millimeters-per-minute": "mm/min", - "kilometer-per-hour-squared": "km/h²", - "foot-per-second-squared": "ft/s²", - "pascal": "Pa", - "kilopascal": "kPa", - "megapascal": "MPa", - "gigapascal": "GPa", - "millibar": "mbar", - "bar": "bar", - "kilobar": "kbar", - "newton": "N", - "newton-meter": "N·m", - "foot-pounds": "ft·lbf", - "inch-pounds": "in·lbf", - "newton-per-meter": "N/m", - "atmospheres": "atm", - "pounds-per-square-inch": "psi", - "torr": "Torr", - "inches-of-mercury": "inHg", - "pascal-per-square-meter": "Pa/m²", - "pound-per-square-inch": "psi", - "newton-per-square-meter": "N/m²", - "kilogram-force-per-square-meter": "kgf/m²", - "pascal-per-square-centimeter": "Pa/cm²", - "ton-force-per-square-inch": "tonf/in²", - "kilonewton-per-square-meter": "kN/m²", - "newton-per-square-millimeter": "N/mm²", - "microjoule": "μJ", - "millijoule": "mJ", - "joule": "J", - "kilojoule": "kJ", - "megajoule": "MJ", - "gigajoule": "GJ", - "watt-hour": "Wh", - "kilowatt-hour": "kWh", - "electron-volts": "eV", - "joules-per-coulomb": "J/C", - "british-thermal-unit": "BTU", - "foot-pound": "ft·lb", - "calorie": "cal", - "small-calorie": "cal", - "kilocalorie": "kcal", - "joule-per-kelvin": "J/K", - "joule-per-kilogram-kelvin": "J/(kg·K)", - "joule-per-kilogram": "J/kg", - "watt-per-meter-kelvin": "W/(m·K)", - "joule-per-cubic-meter": "J/m³", - "therm": "thm", - "electric-dipole-moment": "Debye", - "magnetic-dipole-moment": "Am²", - "debye": "D", - "coulomb-per-square-meter-per-volt": "C/(m²·V)", - "milliwatt": "mW", - "microwatt": "μW", - "watt": "W", - "kilowatt": "kW", - "megawatt": "MW", - "gigawatt": "GW", - "metric-horsepower": "PS", - "milliwatt-per-square-centimeter": "mW/cm²", - "watt-per-square-centimeter": "W/cm²", - "kilowatt-per-square-centimeter": "kW/cm²", - "milliwatt-per-square-meter": "mW/m²", - "watt-per-square-meter": "W/m²", - "kilowatt-per-square-meter": "kW/m²", - "watt-per-square-inch": "W/in²", - "kilowatt-per-square-inch": "kW/in²", - "horsepower": "hp", - "btu-per-hour": "BTU/h", - "coulomb": "C", - "millicoulomb": "mC", - "microcoulomb": "μC", - "picocoulomb": "pC", - "coulomb-per-meter": "C/m", - "coulomb-per-cubic-meter": "C/m³", - "coulomb-per-square-meter": "C/m²", - "square-millimeter": "mm²", - "square-centimeter": "cm²", - "square-meter": "m²", - "hectare": "ha", - "square-kilometer": "km²", - "square-inch": "in²", - "square-foot": "ft²", - "square-yard": "yd²", - "acre": "ac", - "square-mile": "mi²", - "are": "a", - "barn": "b", - "circular-inch": "c in²", - "milliampere-hour": "mAh", - "ampere-hours": "Ah", - "kiloampere-hours": "kAh", - "nanoampere": "nA", - "picoampere": "pA", - "microampere": "μA", - "milliampere": "mA", - "ampere": "A", - "kiloamperes": "kA", - "microampere-per-square-centimeter": "μA/cm²", - "ampere-per-square-meter": "A/m²", - "ampere-per-meter": "A/m", - "oersted": "Oe", - "bohr-magneton": "μB", - "ampere-meter-squared": "A·m²", - "ampere-meter": "A·m", - "nanovolt": "nV", - "picovolt": "pV", - "millivolts": "mV", - "microvolts": "μV", - "volt": "V", - "kilovolts": "kV", - "dbmV": "dBmV", - "dbm": "dBm", - "volt-meter": "V·m", - "kilovolt-meter": "kV·m", - "megavolt-meter": "MV·m", - "microvolt-meter": "μV·m", - "millivolt-meter": "mV·m", - "nanovolt-meter": "nV·m", - "ohm": "Ω", - "microohm": "μΩ", - "milliohm": "mΩ", - "kilohm": "kΩ", - "megohm": "MΩ", - "gigohm": "GΩ", - "hertz": "Hz", - "kilohertz": "kHz", - "megahertz": "MHz", - "gigahertz": "GHz", - "rpm": "rpm", - "candela-per-square-meter": "cd/m²", - "candela": "cd", - "lumen": "lm", - "lux": "lx", - "foot-candle": "fc", - "lumen-per-square-meter": "lm/m²", - "lux-second": "lx·s", - "lumen-second": "lm·s", - "lumens-per-watt": "lm/W", - "absorbance": "AU", - "mole": "mol", - "nanomole": "nmol", - "micromole": "μmol", - "millimole": "mmol", - "kilomole": "kmol", - "mole-per-cubic-meter": "mol/m³", - "rssi": "RSSI", - "ppm": "ppm", - "ppb": "ppb", - "micrograms-per-cubic-meter": "μg/m³", - "aqi": "AQI", - "gram-per-cubic-meter": "g/m³", - "gram-per-kilogram": "g/kg", - "millimeters-per-second": "mm/s", - "neper": "Np", - "bel": "B", - "decibel": "dB", - "meters-per-second-squared": "m/s²", - "becquerel": "Bq", - "curie": "Ci", - "gray": "Gy", - "sievert": "Sv", - "roentgen": "R", - "cps": "cps", - "rad": "rad", - "rem": "rem", - "dps": "dps", - "rutherford": "Rd", - "coulombs-per-kilogram": "C/kg", - "becquerels-per-cubic-meter": "Bq/m³", - "curies-per-liter": "Ci/L", - "becquerels-per-second": "Bq/s", - "curies-per-second": "Ci/s", - "gy-per-second": "Gy/s", - "watt-per-steradian": "W/sr", - "watt-per-square-metre-steradian": "W/(m²·sr)", - "ph-level": "pH", - "turbidity": "NTU", - "mg-per-liter": "mg/L", - "microsiemens-per-centimeter": "μS/cm", - "millisiemens-per-meter": "mS/m", - "siemens-per-meter": "S/m", - "kilogram-per-cubic-meter": "kg/m³", - "gram-per-cubic-centimeter": "g/cm³", - "kilogram-per-square-meter": "kg/m²", - "milligram-per-milliliter": "mg/mL", - "pound-per-cubic-foot": "lb/ft³", - "ounces-per-cubic-inch": "oz/in³", - "tons-per-cubic-yard": "ton/yd³", - "particle-density": "PD", - "kilometers-per-liter": "km/L", - "miles-per-gallon": "mpg", - "liters-per-100-km": "L/100 km", - "gallons-per-mile": "gal/mi", - "liters-per-hour": "L/h", - "gallons-per-hour": "gal/h", - "beats-per-minute": "bpm", - "millimeters-of-mercury": "mmHg", - "milligrams-per-deciliter": "mg/dL", - "g-force": "G", - "kilonewton": "kN", - "kilogram-force": "kgf", - "pound-force": "lbf", - "kilopound-force": "kip", - "dyne": "dyn", - "poundal": "pdl", - "kip": "kip", - "gal": "Gal", - "gravity": "g₀", - "hectopascal": "hPa", - "atmosphere": "atm", - "millibars": "mbar", - "inch-of-mercury": "inHg", - "richter-scale": "Richter", - "second": "s", - "minute": "min", - "hour": "hr", - "day": "day", - "week": "wk", - "month": "mo", - "year": "yr", - "cubic-foot-per-minute": "ft³/min", - "cubic-meters-per-hour": "m³/h", - "cubic-meters-per-second": "m³/s", - "liter-per-second": "L/s", - "liter-per-minute": "L/min", - "gallons-per-minute": "GPM", - "cubic-foot-per-second": "ft³/s", - "milliliters-per-minute": "mL/min", - "bit": "bit", - "byte": "B", - "kilobyte": "KB", - "megabyte": "MB", - "gigabyte": "GB", - "terabyte": "TB", - "petabyte": "PB", - "exabyte": "EB", - "zettabyte": "ZB", - "yottabyte": "YB", - "bit-per-second": "bps", - "kilobit-per-second": "Kbps", - "megabit-per-second": "Mbps", - "gigabit-per-second": "Gbps", - "terabit-per-second": "Tbps", - "byte-per-second": "B/s", - "kilobyte-per-second": "KB/s", - "megabyte-per-second": "MB/s", - "gigabyte-per-second": "GB/s", - "degree": "°", - "radian": "rad", - "gradian": "grad", - "mil": "mil", - "revolution": "rev", - "siemens": "S", - "millisiemens": "mS", - "microsiemens": "μS", - "kilosiemens": "kS", - "megasiemens": "MS", - "gigasiemens": "GS", - "farad": "F", - "millifarad": "mF", - "microfarad": "μF", - "nanofarad": "nF", - "picofarad": "pF", - "kilofarad": "kF", - "megafarad": "MF", - "gigafarad": "GF", - "terfarad": "TF", - "farad-per-meter": "F/m", - "tesla": "T", - "gauss": "G", - "kilogauss": "kG", - "millitesla": "mT", - "microtesla": "μT", - "nanotesla": "nT", - "kilotesla": "kT", - "megatesla": "MT", - "millitesla-square-meters": "mT·m²", - "gamma": "γ", - "lambda": "λ", - "square-meter-per-second": "m²/s", - "square-centimeter-per-second": "cm²/s", - "stoke": "St", - "centistokes": "cSt", - "square-foot-per-second": "ft²/s", - "square-inch-per-second": "in²/s", - "pascal-second": "Pa·s", - "centipoise": "cP", - "poise": "P", - "reynolds": "Re", - "pound-per-foot-hour": "lb/(ft·hr)", - "newton-second-per-square-meter": "N·s/m²", - "dyne-second-per-square-centimeter": "dyn·s/cm²", - "kilogram-per-meter-second": "kg/(m·s)", - "tesla-square-meters": "T·m²", - "maxwell": "Mx", - "tesla-per-meter": "T/m", - "gauss-per-centimeter": "G/cm", - "weber": "Wb", - "microweber": "μWb", - "milliweber": "mWb", - "gauss-square-centimeter": "G·cm²", - "kilogauss-square-centimeter": "kG·cm²", - "henry": "H", - "millihenry": "mH", - "microhenry": "μH", - "nanohenry": "nH", - "henry-per-meter": "H/m", - "tesla-meter-per-ampere": "T·m/A", - "gauss-per-oersted": "G/Oe", - "kilogram-per-mole": "kg/mol", - "gram-per-mole": "g/mol", - "milligram-per-mole": "mg/mol", - "joule-per-mole": "J/mol", - "joule-per-mole-kelvin": "J/mol-K", - "millivolts-per-meter": "mV/m", - "volts-per-meter": "V/m", - "kilovolts-per-meter": "kV/m", - "radian-per-second": "rad/s", - "radian-per-second-squared": "rad/s^2", - "revolutions-per-minute-per-second": "rpm/s", - "revolutions-per-minute-per-second-squared": "rpm/s^2", - "deg-per-second": "deg/s", - "degrees-brix": "°Bx", - "katal": "kat", - "katal-per-cubic-metre": "kat/m^3", - "milliampere-hour-tags": "mAh", - "ampere-hours-tags": "Ah", - "kiloampere-hours-tags": "kAh", - "nanoampere-tags": "nA", - "picoampere-tags": "pA", - "microampere-tags": "μA", - "milliampere-tags": "mA", - "ampere-tags": "A", - "kiloamperes-tags": "kA", - "microampere-per-square-centimeter-tags": "µA/cm²", - "ampere-per-square-meter-tags": "A/m²", - "ampere-per-meter-tags": "A/m", - "oersted-tags": "Oe", - "bohr-magneton-tags": "μB", - "ampere-meter-squared-tags": "A·m²", - "ampere-meter-tags": "A·m", - "milligram-per-cubic-meter": "mg/m³" + "millimeter": "毫米", + "centimeter": "厘米", + "angstrom": "埃", + "nanometer": "纳米", + "micrometer": "微米", + "meter": "米", + "kilometer": "千米", + "inch": "英寸", + "foot": "英尺", + "yard": "码", + "mile": "英里", + "nautical-mile": "海里", + "astronomical-unit": "天文单位", + "reciprocal-metre": "倒数米", + "meter-per-meter": "米/米", + "steradian": "球面度", + "thou": "千分之一英寸", + "barleycorn": "麦粒", + "hand": "手", + "chain": "链", + "furlong": "英寻", + "league": "里", + "fathom": "英寻", + "cable": "缆", + "link": "连", + "rod": "竿", + "nanogram": "纳克", + "microgram": "微克", + "milligram": "毫克", + "gram": "克", + "kilogram": "千克", + "tonne": "公吨", + "ounce": "盎司", + "pound": "磅", + "stone": "英石", + "hundredweight-count": "百磅", + "short-tons": "短吨", + "dalton": "道尔顿", + "grain": "格令", + "drachm": "打兰", + "quarter": "夸特", + "slug": "斯勒格", + "carat": "克拉", + "cubic-millimeter": "立方毫米", + "cubic-centimeter": "立方厘米", + "cubic-meter": "立方米", + "cubic-kilometer": "立方千米", + "microliter": "微升", + "milliliter": "毫升", + "liter": "升", + "hectoliter": "公石", + "cubic-inch": "立方英寸", + "cubic-foot": "立方英尺", + "cubic-yard": "立方码", + "fluid-ounce": "液盎司", + "pint": "品脱", + "quart": "夸脱", + "gallon": "加仑", + "oil-barrels": "桶", + "cubic-meter-per-kilogram": "立方米/千克", + "gill": "吉尔", + "hogshead": "大桶", + "teaspoon": "茶匙", + "tablespoon": "汤匙", + "cup": "杯", + "celsius": "摄氏度", + "kelvin": "开尔文", + "rankine": "兰氏度", + "fahrenheit": "华氏度", + "percent": "百分比", + "meter-per-second": "米/秒", + "kilometer-per-hour": "千米/时", + "foot-per-second": "英尺/秒", + "mile-per-hour": "英里/时", + "knot": "节", + "millimeters-per-minute": "毫米/分钟", + "kilometer-per-hour-squared": "千米/二次方时", + "foot-per-second-squared": "英尺/二次方秒", + "pascal": "帕斯卡", + "kilopascal": "千帕", + "megapascal": "兆帕", + "gigapascal": "吉帕", + "millibar": "毫巴", + "bar": "巴", + "kilobar": "千巴", + "newton": "牛顿", + "newton-meter": "牛顿米", + "foot-pounds": "英尺磅", + "inch-pounds": "英寸磅", + "newton-per-meter": "牛顿/米", + "atmospheres": "大气压", + "pounds-per-square-inch": "磅/平方英寸", + "torr": "托", + "inches-of-mercury": "英寸汞柱", + "pascal-per-square-meter": "帕斯卡/平方米", + "pound-per-square-inch": "磅/平方英寸", + "newton-per-square-meter": "牛顿/平方米", + "kilogram-force-per-square-meter": "千克力/平方米", + "pascal-per-square-centimeter": "帕斯卡/平方厘米", + "ton-force-per-square-inch": "吨力/平方英寸", + "kilonewton-per-square-meter": "千牛/平方米", + "newton-per-square-millimeter": "牛顿/平方毫米", + "microjoule": "微焦", + "millijoule": "毫焦", + "joule": "焦", + "kilojoule": "千焦", + "megajoule": "兆焦", + "gigajoule": "吉焦", + "watt-hour": "瓦时", + "kilowatt-hour": "千瓦时", + "electron-volts": "电子伏", + "joules-per-coulomb": "焦耳/库仑", + "british-thermal-unit": "英国热单位", + "foot-pound": "英尺磅", + "calorie": "卡路里", + "small-calorie": "小卡", + "kilocalorie": "千卡", + "joule-per-kelvin": "焦耳/开", + "joule-per-kilogram-kelvin": "焦耳/千克·开", + "joule-per-kilogram": "焦耳/千克", + "watt-per-meter-kelvin": "瓦/米·开", + "joule-per-cubic-meter": "焦耳/立方米", + "therm": "热", + "electric-dipole-moment": "电偶极矩", + "magnetic-dipole-moment": "磁偶极矩", + "debye": "戴拜", + "coulomb-per-square-meter-per-volt": "库仑·平方米/伏", + "milliwatt": "毫瓦", + "microwatt": "微瓦", + "watt": "瓦", + "kilowatt": "千瓦", + "megawatt": "兆瓦", + "gigawatt": "吉瓦", + "metric-horsepower": "公制马力", + "milliwatt-per-square-centimeter": "毫瓦/平方厘米", + "watt-per-square-centimeter": "瓦/平方厘米", + "kilowatt-per-square-centimeter": "千瓦/平方厘米", + "milliwatt-per-square-meter": "毫瓦/平方米", + "watt-per-square-meter": "瓦/平方米", + "kilowatt-per-square-meter": "千瓦/平方米", + "watt-per-square-inch": "瓦/平方英寸", + "kilowatt-per-square-inch": "千瓦/平方英寸", + "horsepower": "马力", + "btu-per-hour": "英国热单位/时", + "coulomb": "库仑", + "millicoulomb": "毫库仑", + "microcoulomb": "微库仑", + "picocoulomb": "皮库仑", + "coulomb-per-meter": "库仑/米", + "coulomb-per-cubic-meter": "库仑/立方米", + "coulomb-per-square-meter": "库仑/平方米", + "square-millimeter": "平方毫米", + "square-centimeter": "平方厘米", + "square-meter": "平方米", + "hectare": "公顷", + "square-kilometer": "平方千米", + "square-inch": "平方英寸", + "square-foot": "平方英尺", + "square-yard": "平方码", + "acre": "英亩", + "square-mile": "平方英里", + "are": "公亩", + "barn": "bar", + "circular-inch": "圆英寸", + "milliampere-hour": "毫安时", + "milliampere-hour-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 毫安时, 毫安时, mAh", + "ampere-hours": "安时", + "ampere-hours-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 安培, 安时, Ah", + "kiloampere-hours": "千安时", + "kiloampere-hours-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 千安时, 千安时, kAh", + "nanoampere": "纳安培", + "nanoampere-tags": "电流, 安培, 纳安培, nA", + "picoampere": "皮安培", + "picoampere-tags": "电流, 安培, 皮安培, pA", + "microampere": "微安培", + "microampere-tags": "电流, 微安培, 微安培, μA", + "milliampere": "毫安培", + "milliampere-tags": "电流, 毫安培, 毫安培, mA", + "ampere": "安培", + "ampere-tags": "电流, 电流流动, 电流, 电气流, 安培, 安培, 电流, A", + "kiloamperes": "千安培", + "kiloamperes-tags": "电流, 电流流动, 千安培, kA", + "microampere-per-square-centimeter": "微安培/平方厘米", + "microampere-per-square-centimeter-tags": "电流密度, 每平方厘米的微安培, µA/cm²", + "ampere-per-square-meter": "安培/平方米", + "ampere-per-square-meter-tags": "电流密度, 单位面积电流, 每平方米的安培, A/m²", + "ampere-per-meter": "安培/米", + "ampere-per-meter-tags": "磁场强度, 磁场强度, 每米的安培, A/m", + "oersted": "厄斯特德", + "oersted-tags": "磁场, 厄斯特德, Oe", + "bohr-magneton": "玻尔磁子", + "bohr-magneton-tags": "原子物理, 磁矩, 玻尔磁子, μB", + "ampere-meter-squared": "安培·平方米", + "ampere-meter-squared-tags": "磁矩, 偶极矩, 安培·平方米, A·m²", + "ampere-meter": "安培·米", + "ampere-meter-tags": "磁场, 电流环, 安培·米, A·m", + "nanovolt": "纳伏", + "picovolt": "皮伏", + "millivolts": "毫伏", + "microvolts": "微伏", + "volt": "伏", + "kilovolts": "千伏", + "dbmV": "分贝毫伏", + "dbm": "分贝毫瓦", + "volt-meter": "伏·米", + "kilovolt-meter": "千伏·米", + "megavolt-meter": "兆伏·米", + "microvolt-meter": "微伏·米", + "millivolt-meter": "毫伏·米", + "nanovolt-meter": "纳伏·米", + "ohm": "欧姆", + "microohm": "微欧", + "milliohm": "毫欧", + "kilohm": "千欧", + "megohm": "兆欧", + "gigohm": "吉欧", + "hertz": "赫兹", + "kilohertz": "千赫", + "megahertz": "兆赫", + "gigahertz": "吉赫", + "rpm": "转数/分钟", + "candela-per-square-meter": "坎德拉/平方米", + "candela": "坎德拉", + "lumen": "流明", + "lux": "勒克斯", + "foot-candle": "英尺烛光", + "lumen-per-square-meter": "流明/平方米", + "lux-second": "勒克斯·秒", + "lumen-second": "流明·秒", + "lumens-per-watt": "流明/瓦", + "absorbance": "吸光度", + "mole": "摩尔", + "nanomole": "纳摩尔", + "micromole": "微摩尔", + "millimole": "毫摩尔", + "kilomole": "千摩尔", + "mole-per-cubic-meter": "摩尔/立方米", + "rssi": "接收信号强度指示", + "ppm": "百万分之一", + "ppb": "十亿分之一", + "micrograms-per-cubic-meter": "微克/立方米", + "aqi": "空气质量指数", + "gram-per-cubic-meter": "克/立方米", + "gram-per-kilogram": "比湿", + "millimeters-per-second": "毫米/秒", + "neper": "纳珀", + "bel": "贝尔", + "decibel": "分贝", + "meters-per-second-squared": "平方米/秒", + "becquerel": "贝克勒", + "curie": "居里", + "gray": "戈瑞", + "sievert": "西弗特", + "roentgen": "伦琴", + "cps": "计数/秒", + "rad": "拉德", + "rem": "剂量当量", + "dps": "衰变/秒", + "rutherford": "卢瑟福", + "coulombs-per-kilogram": "库仑/千克", + "becquerels-per-cubic-meter": "贝克勒/立方米", + "curies-per-liter": "居里/升", + "becquerels-per-second": "贝克勒/秒", + "curies-per-second": "居里/秒", + "gy-per-second": "戈瑞/秒", + "watt-per-steradian": "瓦/立体弧度", + "watt-per-square-metre-steradian": "瓦/平方米·弧度", + "ph-level": "酸碱度", + "turbidity": "浊度", + "mg-per-liter": "毫克/升", + "microsiemens-per-centimeter": "微西门子/厘米", + "millisiemens-per-meter": "毫西门子/米", + "siemens-per-meter": "西门子/米", + "kilogram-per-cubic-meter": "千克/立方米", + "gram-per-cubic-centimeter": "克/立方厘米", + "kilogram-per-square-meter": "千克/平方米", + "milligram-per-milliliter": "毫克/毫升", + "milligram-per-cubic-meter": "毫克/立方米", + "pound-per-cubic-foot": "磅/立方英尺", + "ounces-per-cubic-inch": "盎司/立方英寸", + "tons-per-cubic-yard": "吨/立方码", + "particle-density": "颗粒密度", + "kilometers-per-liter": "千米/升", + "miles-per-gallon": "英里/加仑", + "liters-per-100-km": "升/100千米", + "gallons-per-mile": "加仑/英里", + "liters-per-hour": "升/小时", + "gallons-per-hour": "加仑/小时", + "beats-per-minute": "每分钟心跳数", + "millimeters-of-mercury": "毫米汞柱", + "milligrams-per-deciliter": "毫克/分升", + "g-force": "重力加速度", + "kilonewton": "千牛", + "kilogram-force": "千克力", + "pound-force": "磅力", + "kilopound-force": "千磅力", + "dyne": "达因", + "poundal": "磅力秒平方英尺", + "kip": "千磅力", + "gal": "加尔", + "gravity": "重力", + "hectopascal": "百帕", + "atmosphere": "大气压", + "millibars": "毫巴", + "inch-of-mercury": "英寸汞柱", + "richter-scale": "里氏震级", + "second": "秒", + "minute": "分钟", + "hour": "小时", + "day": "天", + "week": "周", + "month": "月", + "year": "年", + "cubic-foot-per-minute": "立方英尺/分钟", + "cubic-meters-per-hour": "立方米/小时", + "cubic-meters-per-second": "立方米/秒", + "liter-per-second": "升/秒", + "liter-per-minute": "升/分钟", + "gallons-per-minute": "加仑/分钟", + "cubic-foot-per-second": "立方英尺/秒", + "milliliters-per-minute": "毫升/分钟", + "bit": "位", + "byte": "字节", + "kilobyte": "千字节", + "megabyte": "兆字节", + "gigabyte": "吉字节", + "terabyte": "太字节", + "petabyte": "拍字节", + "exabyte": "艾字节", + "zettabyte": "泽字节", + "yottabyte": "尧字节", + "bit-per-second": "比特/秒", + "kilobit-per-second": "千比特/秒", + "megabit-per-second": "兆比特/秒", + "gigabit-per-second": "吉比特/秒", + "terabit-per-second": "太比特/秒", + "byte-per-second": "字节/秒", + "kilobyte-per-second": "千字节/秒", + "megabyte-per-second": "兆字节/秒", + "gigabyte-per-second": "吉字节/秒", + "degree": "度", + "radian": "弧度", + "gradian": "百分度", + "mil": "米尔", + "revolution": "圈", + "siemens": "西门子", + "millisiemens": "毫西门子", + "microsiemens": "微西门子", + "kilosiemens": "千西门子", + "megasiemens": "兆西门子", + "gigasiemens": "吉西门子", + "farad": "法拉", + "millifarad": "毫法拉", + "microfarad": "微法拉", + "nanofarad": "纳法拉", + "picofarad": "皮法拉", + "kilofarad": "千法拉", + "megafarad": "兆法拉", + "gigafarad": "吉法拉", + "terfarad": "太法拉", + "farad-per-meter": "法拉/米", + "tesla": "特斯拉", + "gauss": "高斯", + "kilogauss": "千高斯", + "millitesla": "毫特斯拉", + "microtesla": "微特斯拉", + "nanotesla": "纳特斯拉", + "kilotesla": "千特斯拉", + "megatesla": "兆特斯拉", + "millitesla-square-meters": "毫特斯拉平方米", + "gamma": "伽马", + "lambda": "兰姆达", + "square-meter-per-second": "平方米/秒", + "square-centimeter-per-second": "平方厘米/秒", + "stoke": "斯托克", + "centistokes": "厘斯托克", + "square-foot-per-second": "平方英尺/秒", + "square-inch-per-second": "平方英寸/秒", + "pascal-second": "帕斯卡秒", + "centipoise": "厘泊", + "poise": "泊", + "reynolds": "雷诺", + "pound-per-foot-hour": "磅/英尺-小时", + "newton-second-per-square-meter": "牛顿秒/平方米", + "dyne-second-per-square-centimeter": "达因秒/平方厘米", + "kilogram-per-meter-second": "千克/米·秒", + "tesla-square-meters": "特斯拉平方米", + "maxwell": "麦克斯韦", + "tesla-per-meter": "特斯拉/米", + "gauss-per-centimeter": "高斯/厘米", + "weber": "韦伯", + "microweber": "微韦伯", + "milliweber": "毫韦伯", + "gauss-square-centimeter": "高斯平方厘米", + "kilogauss-square-centimeter": "千高斯平方厘米", + "henry": "亨利", + "millihenry": "毫亨利", + "microhenry": "微亨利", + "nanohenry": "纳亨利", + "henry-per-meter": "亨利/米", + "tesla-meter-per-ampere": "特斯拉米/安", + "gauss-per-oersted": "高斯/厘斯特", + "kilogram-per-mole": "千克/摩尔", + "gram-per-mole": "克/摩尔", + "milligram-per-mole": "毫克/摩尔", + "joule-per-mole": "焦耳/摩尔", + "joule-per-mole-kelvin": "焦耳/摩尔·开尔文", + "millivolts-per-meter": "毫伏/米", + "volts-per-meter": "伏/米", + "kilovolts-per-meter": "千伏/米", + "radian-per-second": "弧度/秒", + "radian-per-second-squared": "弧度/二次方秒", + "revolutions-per-minute-per-second": "角加速度", + "revolutions-per-minute-per-second-squared": "角加速度", + "deg-per-second": "度/秒", + "degrees-brix": "白利糖度", + "katal": "卡塔尔", + "katal-per-cubic-metre": "卡塔尔/立方米" }, "user": { "user": "用户", From ad90b4480c6b0d0fbcaf0e8f1e4cd69f14a4090e Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 19 Jan 2024 11:22:45 +0200 Subject: [PATCH 024/137] Introduce EDGE_CONNECTIVITY and EDGE_FAILURE notification rules to inform about edge's status and errors with connection, bad credentials, etc --- .../install/ThingsboardInstallService.java | 1 + .../service/edge/EdgeContextComponent.java | 4 ++ .../service/edge/rpc/EdgeGrpcService.java | 26 +++++-- .../service/edge/rpc/EdgeGrpcSession.java | 36 +++++++--- .../DefaultSystemDataLoaderService.java | 13 ++++ .../DefaultNotificationCenter.java | 10 ++- .../EdgeConnectivityTriggerProcessor.java | 60 ++++++++++++++++ .../trigger/EdgeFailureTriggerProcessor.java | 54 +++++++++++++++ .../impl/NotificationRuleExportService.java | 20 ++++-- .../impl/NotificationRuleImportService.java | 15 +++- .../controller/AbstractNotifyEntityTest.java | 20 +----- .../service/limits/RateLimitServiceTest.java | 6 +- .../server/common/data/limit/LimitedApi.java | 2 + .../data/notification/NotificationType.java | 5 +- .../EdgeConnectivityNotificationInfo.java | 67 ++++++++++++++++++ .../info/EdgeFailureNotificationInfo.java | 68 +++++++++++++++++++ .../rule/trigger/EdgeConnectivityTrigger.java | 63 +++++++++++++++++ .../rule/trigger/EdgeFailureTrigger.java | 62 +++++++++++++++++ ...ectivityNotificationRuleTriggerConfig.java | 46 +++++++++++++ ...eFailureNotificationRuleTriggerConfig.java | 39 +++++++++++ .../config/NotificationRuleTriggerConfig.java | 2 + .../config/NotificationRuleTriggerType.java | 2 + .../DefaultTenantProfileConfiguration.java | 3 + .../server/dao/edge/BaseEdgeEventService.java | 11 ++- .../DefaultNotificationSettingsService.java | 39 +++++++++-- .../notification/DefaultNotifications.java | 35 +++++++++- .../dao/service/EdgeEventServiceTest.java | 24 +++---- .../rule-notification-dialog.component.html | 47 +++++++++++++ .../rule-notification-dialog.component.ts | 7 ++ .../app/shared/models/limited-api.models.ts | 8 ++- .../app/shared/models/notification.models.ts | 22 +++++- .../help/en_US/notification/edge_connected.md | 57 ++++++++++++++++ .../help/en_US/notification/edge_failure.md | 57 ++++++++++++++++ .../help/en_US/notification/rate_limits.md | 2 +- .../assets/locale/locale.constant-en_US.json | 10 ++- 35 files changed, 868 insertions(+), 75 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeFailureNotificationInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java create mode 100644 ui-ngx/src/assets/help/en_US/notification/edge_connected.md create mode 100644 ui-ngx/src/assets/help/en_US/notification/edge_failure.md diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 85a6e061d0..81b17f3402 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -279,6 +279,7 @@ public class ThingsboardInstallService { case "3.6.2": log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); + systemDataLoaderService.updateDefaultNotificationConfigs(); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index a10a48244b..2f157af70e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; @@ -149,6 +150,9 @@ public class EdgeContextComponent { @Autowired private ResourceService resourceService; + @Autowired + private NotificationRuleProcessor notificationRuleProcessor; + @Autowired private AlarmEdgeProcessor alarmProcessor; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index d3262039d5..09092d0621 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectivityTrigger; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -263,7 +264,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) { - TenantId tenantId = edgeGrpcSession.getEdge().getTenantId(); + Edge edge = edgeGrpcSession.getEdge(); + TenantId tenantId = edge.getTenantId(); log.info("[{}][{}] edge [{}] connected successfully.", tenantId, edgeGrpcSession.getSessionId(), edgeId); sessions.put(edgeId, edgeGrpcSession); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); @@ -276,7 +278,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); long lastConnectTs = System.currentTimeMillis(); save(tenantId, edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs); - pushRuleEngineMessage(tenantId, edgeId, lastConnectTs, TbMsgType.CONNECT_EVENT); + pushRuleEngineMessage(tenantId, edge, lastConnectTs, TbMsgType.CONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); scheduleEdgeEventsCheck(edgeGrpcSession); } @@ -381,7 +383,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } - private void onEdgeDisconnect(EdgeId edgeId, UUID sessionId) { + private void onEdgeDisconnect(Edge edge, UUID sessionId) { + EdgeId edgeId = edge.getId(); log.info("[{}][{}] edge disconnected!", edgeId, sessionId); EdgeGrpcSession toRemove = sessions.get(edgeId); if (toRemove.getSessionId().equals(sessionId)) { @@ -397,7 +400,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i save(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); save(tenantId, edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, lastDisconnectTs); - pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edgeId, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); + pushRuleEngineMessage(toRemove.getEdge().getTenantId(), edge, lastDisconnectTs, TbMsgType.DISCONNECT_EVENT); cancelScheduleEdgeEventsCheck(edgeId); } else { log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId); @@ -452,16 +455,24 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } } - private void pushRuleEngineMessage(TenantId tenantId, EdgeId edgeId, long ts, TbMsgType msgType) { + private void pushRuleEngineMessage(TenantId tenantId, Edge edge, long ts, TbMsgType msgType) { try { + EdgeId edgeId = edge.getId(); ObjectNode edgeState = JacksonUtil.newObjectNode(); - if (msgType.equals(TbMsgType.CONNECT_EVENT)) { + boolean isConnected = TbMsgType.CONNECT_EVENT.equals(msgType); + if (isConnected) { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, true); edgeState.put(DefaultDeviceStateService.LAST_CONNECT_TIME, ts); } else { edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, false); edgeState.put(DefaultDeviceStateService.LAST_DISCONNECT_TIME, ts); } + ctx.getNotificationRuleProcessor().process(EdgeConnectivityTrigger.builder() + .tenantId(tenantId) + .customerId(edge.getCustomerId()) + .edgeId(edgeId) + .edgeName(edge.getName()) + .connected(isConnected).build()); String data = JacksonUtil.toString(edgeState); TbMsgMetaData md = new TbMsgMetaData(); if (!persistToTelemetry) { @@ -470,7 +481,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i TbMsg tbMsg = TbMsg.newMsg(msgType, edgeId, md, TbMsgDataType.JSON, data); clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null); } catch (Exception e) { - log.warn("[{}][{}] Failed to push {}", tenantId, edgeId, msgType, e); + log.warn("[{}][{}] Failed to push {}", tenantId, edge.getId(), msgType, e); } } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index f1f473f976..a42a80c318 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeFailureTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -110,7 +111,7 @@ public final class EdgeGrpcSession implements Closeable { private final UUID sessionId; private final BiConsumer sessionOpenListener; - private final BiConsumer sessionCloseListener; + private final BiConsumer sessionCloseListener; private final EdgeSessionState sessionState = new EdgeSessionState(); @@ -136,7 +137,7 @@ public final class EdgeGrpcSession implements Closeable { private ScheduledExecutorService sendDownlinkExecutorService; EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, BiConsumer sessionOpenListener, - BiConsumer sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize) { + BiConsumer sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize) { this.sessionId = UUID.randomUUID(); this.ctx = ctx; this.outputStream = outputStream; @@ -205,7 +206,7 @@ public final class EdgeGrpcSession implements Closeable { connected = false; if (edge != null) { try { - sessionCloseListener.accept(edge.getId(), sessionId); + sessionCloseListener.accept(edge, sessionId); } catch (Exception ignored) { } } @@ -313,7 +314,7 @@ public final class EdgeGrpcSession implements Closeable { } catch (Exception e) { log.error("[{}][{}] Failed to send downlink message [{}]", this.tenantId, this.sessionId, downlinkMsg, e); connected = false; - sessionCloseListener.accept(edge.getId(), sessionId); + sessionCloseListener.accept(edge, sessionId); } finally { downlinkMsgLock.unlock(); } @@ -465,7 +466,14 @@ public final class EdgeGrpcSession implements Closeable { if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); if (attempt > 1) { - log.warn("[{}][{}] Failed to deliver the batch: {}, attempt: {}", this.tenantId, this.sessionId, copy, attempt); + String errorMsg = String.format("Failed to deliver the batch: {%s}", copy); + if (attempt == 2) { + // Send a failure notification only on the second attempt. + // This ensures that failure alerts are sent just once to avoid redundant notifications. + ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId) + .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + } + log.warn("[{}][{}] {}, attempt: {}", this.tenantId, this.sessionId, errorMsg, attempt); } log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { @@ -484,8 +492,11 @@ public final class EdgeGrpcSession implements Closeable { if (attempt < MAX_DOWNLINK_ATTEMPTS) { scheduleDownlinkMsgsPackSend(attempt + 1); } else { + String errorMsg = String.format("Failed to deliver messages: %s", copy); log.warn("[{}][{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", this.tenantId, this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy); + ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); stopCurrentSendDownlinkMsgsTask(false); } } else { @@ -779,7 +790,10 @@ public final class EdgeGrpcSession implements Closeable { } } } catch (Exception e) { + String errorMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); log.error("[{}][{}] Can't process uplink msg [{}]", this.tenantId, this.sessionId, uplinkMsg, e); + ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); return Futures.immediateFailedFuture(e); } return Futures.allAsList(result); @@ -803,15 +817,21 @@ public final class EdgeGrpcSession implements Closeable { .setMaxInboundMessageSize(maxInboundMessageSize) .build(); } + String errorMsg = String.format("Failed to validate the edge! Provided request secret: %s", request.getEdgeSecret()); + ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) - .setErrorMsg("Failed to validate the edge!") + .setErrorMsg(errorMsg) .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } catch (Exception e) { - log.error("[{}] Failed to process edge connection!", request.getEdgeRoutingKey(), e); + String errorMsg = "Failed to process edge connection!"; + ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + log.error(errorMsg, e); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.SERVER_UNAVAILABLE) - .setErrorMsg("Failed to process edge connection!") + .setErrorMsg(errorMsg) .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index ddc848a91d..fcc829886c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -697,6 +697,19 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void updateDefaultNotificationConfigs() { + PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); + ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4)); + log.info("Updating default edge failure notification configs for all tenants"); + AtomicInteger count = new AtomicInteger(); + for (TenantId tenantId : tenants) { + executor.submit(() -> { + notificationSettingsService.updateDefaultNotificationConfigs(tenantId); + int n = count.incrementAndGet(); + if (n % 500 == 0) { + log.info("{} tenants processed", n); + } + }); + } notificationSettingsService.updateDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index be37d98bb5..5bbde6de81 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -241,13 +241,11 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple case PLATFORM_USERS: { PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration(); if (targetConfig.getUsersFilter().getType().isForRules() && ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) { - recipients = new PageDataIterable<>(pageLink -> { - return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink); - }, 500); + recipients = new PageDataIterable<>(pageLink -> + notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink), 500); } else { - recipients = new PageDataIterable<>(pageLink -> { - return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink); - }, 500); + recipients = new PageDataIterable<>(pageLink -> + notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink), 500); } break; } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java new file mode 100644 index 0000000000..7133bd2d76 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java @@ -0,0 +1,60 @@ +/** + * 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.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.info.EdgeConnectivityNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectivityTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig.EdgeConnectivityEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +@Service +@RequiredArgsConstructor +public class EdgeConnectivityTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(EdgeConnectivityTrigger trigger, EdgeConnectivityNotificationRuleTriggerConfig triggerConfig) { + EdgeConnectivityEvent event = trigger.isConnected() ? EdgeConnectivityEvent.CONNECTED : EdgeConnectivityEvent.DISCONNECTED; + if (!triggerConfig.getNotifyOn().contains(event)) { + return false; + } + if (CollectionUtils.isNotEmpty(triggerConfig.getEdges())) { + return triggerConfig.getEdges().contains(trigger.getEdgeId().getId()); + } + return true; + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeConnectivityTrigger trigger) { + return EdgeConnectivityNotificationInfo.builder() + .eventType(trigger.isConnected() ? "connected" : "disconnected") + .tenantId(trigger.getTenantId()) + .customerId(trigger.getCustomerId()) + .edgeId(trigger.getEdgeId()) + .edgeName(trigger.getEdgeName()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java new file mode 100644 index 0000000000..afdfa6eb1f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java @@ -0,0 +1,54 @@ +/** + * 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.rule.trigger; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.notification.info.EdgeFailureNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeFailureTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +@Service +@RequiredArgsConstructor +public class EdgeFailureTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(EdgeFailureTrigger trigger, EdgeFailureNotificationRuleTriggerConfig triggerConfig) { + if (CollectionUtils.isNotEmpty(triggerConfig.getEdges())) { + return !triggerConfig.getEdges().contains(trigger.getEdgeId().getId()); + } + return true; + } + + @Override + public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeFailureTrigger trigger) { + return EdgeFailureNotificationInfo.builder() + .tenantId(trigger.getTenantId()) + .edgeId(trigger.getEdgeId()) + .customerId(trigger.getCustomerId()) + .edgeName(trigger.getEdgeName()) + .errorMsg(trigger.getErrorMsg()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_FAILURE; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java index 7a7d176f81..61897aaa55 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.sync.ie.EntityExportData; @@ -65,13 +67,24 @@ public class NotificationRuleExportService ruleChains = triggerConfig.getRuleChains(); if (ruleChains != null) { triggerConfig.setRuleChains(toExternalIds(ruleChains, RuleChainId::new, ctx).collect(Collectors.toSet())); } break; + } + case EDGE_CONNECTIVITY: { + EdgeConnectivityNotificationRuleTriggerConfig triggerConfig = (EdgeConnectivityNotificationRuleTriggerConfig) ruleTriggerConfig; + triggerConfig.setEdges(null); + break; + } + case EDGE_FAILURE: { + EdgeFailureNotificationRuleTriggerConfig triggerConfig = (EdgeFailureNotificationRuleTriggerConfig) ruleTriggerConfig; + triggerConfig.setEdges(null); + break; + } } NotificationRuleRecipientsConfig ruleRecipientsConfig = notificationRule.getRecipientsConfig(); @@ -79,9 +92,8 @@ public class NotificationRuleExportService> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable()); - escalationTable.replaceAll((delay, targets) -> { - return toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList()); - }); + escalationTable.replaceAll((delay, targets) -> + toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList())); recipientsConfig.setEscalationTable(escalationTable); break; } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java index 72f0fa219e..c0f3991aca 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/NotificationRuleImportService.java @@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; @@ -86,7 +88,7 @@ public class NotificationRuleImportService extends BaseEntityImportService ruleChains = triggerConfig.getRuleChains(); if (ruleChains != null) { @@ -95,6 +97,17 @@ public class NotificationRuleImportService extends BaseEntityImportService matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); - ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); - ArgumentMatcher matcherCustomerId = customerId == null ? - argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); - ArgumentMatcher matcherUserId = userId == null ? - argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); - testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, - extractMatcherAdditionalInfo(additionalInfo)); - testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); - Mockito.reset(tbClusterService, auditLogService); - } - protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(HasName entity, HasName originator, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, @@ -624,7 +606,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { private String entityClassToString(HasName entity) { String className = entity.getClass().toString() .substring(entity.getClass().toString().lastIndexOf(".") + 1); - List str = className.chars() + List str = className.chars() .mapToObj(x -> (Character.isUpperCase(x)) ? "_" + Character.toString(x) : Character.toString(x)) .collect(Collectors.toList()); return String.join("", str).toUpperCase(Locale.ENGLISH).substring(1); diff --git a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java index 9917e837b9..2d37bfce95 100644 --- a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java @@ -69,6 +69,8 @@ public class RateLimitServiceTest { profileConfiguration.setCustomerServerRestLimitsConfiguration(rateLimit); profileConfiguration.setWsUpdatesPerSessionRateLimit(rateLimit); profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit); + profileConfiguration.setEdgeEventRateLimits(rateLimit); + profileConfiguration.setEdgeEventRateLimitsPerEdge(rateLimit); updateTenantProfileConfiguration(profileConfiguration); for (LimitedApi limitedApi : List.of( @@ -76,7 +78,9 @@ public class RateLimitServiceTest { LimitedApi.ENTITY_IMPORT, LimitedApi.NOTIFICATION_REQUESTS, LimitedApi.REST_REQUESTS_PER_CUSTOMER, - LimitedApi.CASSANDRA_QUERIES + LimitedApi.CASSANDRA_QUERIES, + LimitedApi.EDGE_EVENTS, + LimitedApi.EDGE_EVENTS_PER_EDGE )) { testRateLimits(limitedApi, max, tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java index 5f743f626a..980eb880a0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/limit/LimitedApi.java @@ -31,6 +31,8 @@ public enum LimitedApi { REST_REQUESTS_PER_CUSTOMER(DefaultTenantProfileConfiguration::getCustomerServerRestLimitsConfiguration, "REST API requests per customer", false), WS_UPDATES_PER_SESSION(DefaultTenantProfileConfiguration::getWsUpdatesPerSessionRateLimit, "WS updates per session", true), CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration, "Cassandra queries", true), + EDGE_EVENTS(DefaultTenantProfileConfiguration::getEdgeEventRateLimits, "Edge events", true), + EDGE_EVENTS_PER_EDGE(DefaultTenantProfileConfiguration::getEdgeEventRateLimitsPerEdge, "Edge events per edge", false), PASSWORD_RESET(false, true), TWO_FA_VERIFICATION_CODE_SEND(false, true), TWO_FA_VERIFICATION_CODE_CHECK(false, true), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index 8eb4451e3b..9784209555 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -28,6 +28,7 @@ public enum NotificationType { ENTITIES_LIMIT, API_USAGE_LIMIT, RULE_NODE, - RATE_LIMITS - + RATE_LIMITS, + EDGE_CONNECTIVITY, + EDGE_FAILURE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java new file mode 100644 index 0000000000..3dfe438ada --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java @@ -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. + */ +package org.thingsboard.server.common.data.notification.info; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Map; + +import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EdgeConnectivityNotificationInfo implements RuleOriginatedNotificationInfo { + + private String eventType; + private TenantId tenantId; + private CustomerId customerId; + private EdgeId edgeId; + private String edgeName; + + @Override + public Map getTemplateData() { + return mapOf( + "eventType", eventType, + "tenantId", tenantId.toString(), + "edgeId", edgeId.toString(), + "edgeName", edgeName + ); + } + + @Override + public TenantId getAffectedTenantId() { + return tenantId; + } + + @Override + public CustomerId getAffectedCustomerId() { + return customerId; + } + + @Override + public EntityId getStateEntityId() { + return edgeId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeFailureNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeFailureNotificationInfo.java new file mode 100644 index 0000000000..3db1224705 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeFailureNotificationInfo.java @@ -0,0 +1,68 @@ +/** + * 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.common.data.notification.info; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Map; + +import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EdgeFailureNotificationInfo implements RuleOriginatedNotificationInfo { + + private TenantId tenantId; + private CustomerId customerId; + private EdgeId edgeId; + private String edgeName; + private String errorMsg; + + @Override + public Map getTemplateData() { + return mapOf( + "tenantId", tenantId.toString(), + "edgeId", edgeId.toString(), + "edgeName", edgeName, + "errorMsg", errorMsg + ); + } + + @Override + public TenantId getAffectedTenantId() { + return tenantId; + } + + @Override + public CustomerId getAffectedCustomerId() { + return customerId; + } + + @Override + public EntityId getStateEntityId() { + return edgeId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java new file mode 100644 index 0000000000..dbbf1e3c74 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java @@ -0,0 +1,63 @@ +/** + * 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.common.data.notification.rule.trigger; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Data +@Builder +public class EdgeConnectivityTrigger implements NotificationRuleTrigger { + + private final TenantId tenantId; + private final CustomerId customerId; + private final EdgeId edgeId; + private final boolean connected; + private final String edgeName; + + @Override + public boolean deduplicate() { + return true; + } + + @Override + public String getDeduplicationKey() { + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeName, String.valueOf(connected)); + } + + @Override + public long getDefaultDeduplicationDuration() { + return TimeUnit.HOURS.toMillis(3); + } + + @Override + public NotificationRuleTriggerType getType() { + return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + } + + @Override + public EntityId getOriginatorEntityId() { + return edgeId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java new file mode 100644 index 0000000000..98e1db0527 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java @@ -0,0 +1,62 @@ +/** + * 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.common.data.notification.rule.trigger; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; + +import java.util.concurrent.TimeUnit; + +@Data +@Builder +public class EdgeFailureTrigger implements NotificationRuleTrigger { + + private final TenantId tenantId; + private final CustomerId customerId; + private final EdgeId edgeId; + private final String edgeName; + private final String errorMsg; + + @Override + public boolean deduplicate() { + return true; + } + + @Override + public String getDeduplicationKey() { + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeName, errorMsg); + } + + @Override + public long getDefaultDeduplicationDuration() { + return TimeUnit.HOURS.toMillis(2); + } + + @Override + public NotificationRuleTriggerType getType() { + return NotificationRuleTriggerType.EDGE_FAILURE; + } + + @Override + public EntityId getOriginatorEntityId() { + return edgeId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..bf22487614 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java @@ -0,0 +1,46 @@ +/** + * 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.common.data.notification.rule.trigger.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; +import java.util.Set; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EdgeConnectivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + private Set edges; // if empty - all edges + @NotEmpty + private Set notifyOn; + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + } + + public enum EdgeConnectivityEvent { + CONNECTED, DISCONNECTED + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..7dda80b652 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java @@ -0,0 +1,39 @@ +/** + * 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.common.data.notification.rule.trigger.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class EdgeFailureNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + private Set edges; // if empty - all edges + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_FAILURE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java index 5ffb8a5fe1..ae2130a3f6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java @@ -36,6 +36,8 @@ import java.io.Serializable; @Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT"), @Type(value = ApiUsageLimitNotificationRuleTriggerConfig.class, name = "API_USAGE_LIMIT"), @Type(value = RateLimitsNotificationRuleTriggerConfig.class, name = "RATE_LIMITS"), + @Type(value = EdgeConnectivityNotificationRuleTriggerConfig.class, name = "EDGE_CONNECTIVITY"), + @Type(value = EdgeFailureNotificationRuleTriggerConfig.class, name = "EDGE_FAILURE"), }) public interface NotificationRuleTriggerConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java index 6eff7a8114..93d26140f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java @@ -26,6 +26,8 @@ public enum NotificationRuleTriggerType { ALARM_ASSIGNMENT, DEVICE_ACTIVITY, RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, + EDGE_CONNECTIVITY, + EDGE_FAILURE, NEW_PLATFORM_VERSION(false), ENTITIES_LIMIT(false), API_USAGE_LIMIT(false), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 1a4df035f5..3fe110de0b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -81,6 +81,9 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private String cassandraQueryTenantRateLimitsConfiguration; + private String edgeEventRateLimits; + private String edgeEventRateLimitsPerEdge; + private int defaultStorageTtlDays; private int alarmsTtlDays; private int rpcTtlDays; diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index 9ea70f6c5a..6a05b98d16 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -19,12 +19,16 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.util.limits.RateLimitService; @Service @Slf4j @@ -32,11 +36,16 @@ import org.thingsboard.server.dao.service.DataValidator; public class BaseEdgeEventService implements EdgeEventService { private final EdgeEventDao edgeEventDao; - + private final RateLimitService rateLimitService; private final DataValidator edgeEventValidator; @Override public ListenableFuture saveAsync(EdgeEvent edgeEvent) { + boolean isEdgeEventTenantRateLimitReached = !rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS, edgeEvent.getTenantId()); + boolean isEdgeEventRateLimitPerEdgeReached = !rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS_PER_EDGE, edgeEvent.getTenantId(), edgeEvent.getEdgeId()); + if (isEdgeEventTenantRateLimitReached || isEdgeEventRateLimitPerEdgeReached) { + throw new TbRateLimitsException(EntityType.EDGE); + } edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); return edgeEventDao.saveAsync(edgeEvent); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 59787a01a7..3e02f42cba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.notification.targets.platform.SystemAd import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.settings.UserSettings; import org.thingsboard.server.common.data.settings.UserSettingsType; @@ -53,6 +54,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -187,6 +189,8 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.alarmComment, tenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.alarmAssignment, affectedUser.getId()); defaultNotifications.create(tenantId, DefaultNotifications.ruleEngineComponentLifecycleFailure, tenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.edgeConnectivity, tenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.edgeFailure, tenantAdmins.getId()); } @Override @@ -198,17 +202,40 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS } NotificationTarget sysAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.SYSTEM_ADMINISTRATORS).stream() - .findFirst().orElseGet(() -> { - return createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators"); - }); + .findFirst().orElseGet(() -> createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators")); NotificationTarget affectedTenantAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.AFFECTED_TENANT_ADMINISTRATORS).stream() - .findFirst().orElseGet(() -> { - return createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), ""); - }); + .findFirst().orElseGet(() -> createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), "")); defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); + } else { + List requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTIVITY, NotificationType.EDGE_FAILURE); + List existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( + tenantId, requiredNotificationTypes, new PageLink(1)) + .getData() + .stream() + .map(NotificationTemplate::getNotificationType) + .collect(Collectors.toList()); + + NotificationTarget tenantAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.TENANT_ADMINISTRATORS) + .stream() + .findFirst() + .orElseGet(() -> createTarget(tenantId, "Tenant administrators", new TenantAdministratorsFilter(), + tenantId.isSysTenantId() ? "All tenant administrators" : "Tenant administrators")); + + for (NotificationType type : requiredNotificationTypes) { + if (!existingNotificationTypes.contains(type)) { + switch (type) { + case EDGE_CONNECTIVITY: + defaultNotifications.create(tenantId, DefaultNotifications.edgeConnectivity, tenantAdmins.getId()); + break; + case EDGE_FAILURE: + defaultNotifications.create(tenantId, DefaultNotifications.edgeFailure, tenantAdmins.getId()); + break; + } + } + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 5a78fa9864..4e2cc5bbad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -40,6 +40,9 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm import org.thingsboard.server.common.data.notification.rule.trigger.config.ApiUsageLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig.EdgeConnectivityEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NewPlatformVersionNotificationRuleTriggerConfig; @@ -325,6 +328,36 @@ public class DefaultNotifications { .description("Send notification to tenant admins when any Rule chain or Rule node failed to start, update or stop") .build()) .build(); + public static final DefaultNotification edgeConnectivity = DefaultNotification.builder() + .name("Edge connectivity notification") + .type(NotificationType.EDGE_CONNECTIVITY) + .subject("Edge '${edgeName}' is ${eventType}") + .text("Edge '${edgeName}' is now ${eventType}") + .icon("info").color(null) + .button("Go to Edge").link("/edgeManagement/instances/${edgeId}") + .rule(DefaultRule.builder() + .name("Edge connectivity") + .enabled(false) + .triggerConfig(EdgeConnectivityNotificationRuleTriggerConfig.builder() + .edges(null) + .notifyOn(Set.of(EdgeConnectivityEvent.CONNECTED, EdgeConnectivityEvent.DISCONNECTED)) + .build()) + .description("Send notification to tenant admins when Edge changes its connectivity state") + .build()) + .build(); + public static final DefaultNotification edgeFailure = DefaultNotification.builder() + .name("Edge error notification") + .type(NotificationType.EDGE_FAILURE) + .subject("Edge '${edgeName}' received error") + .text("Error message: '${errorMsg}'") + .icon("error").color(null) + .button("Go to Edge").link("/edgeManagement/instances/${edgeId}") + .rule(DefaultRule.builder() + .name("Edge error") + .triggerConfig(EdgeFailureNotificationRuleTriggerConfig.builder().edges(null).build()) + .description("Send notification to tenant admins or to assigned customers to Edge when error occurs") + .build()) + .build(); public static final DefaultNotification jwtSigningKeyIssue = DefaultNotification.builder() .name("JWT Signing Key issue notification") @@ -346,7 +379,7 @@ public class DefaultNotifications { if (defaultNotification.getRule() != null && targets.length > 0) { NotificationRule rule = defaultNotification.toRule(template.getId(), targets); rule.setTenantId(tenantId); - rule = ruleService.saveNotificationRule(tenantId, rule); + ruleService.saveNotificationRule(tenantId, rule); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/EdgeEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/EdgeEventServiceTest.java index 2a03a1277c..2cc179fae2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/EdgeEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/EdgeEventServiceTest.java @@ -40,7 +40,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; -import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT; @DaoSqlTest public class EdgeEventServiceTest extends AbstractServiceTest { @@ -56,19 +56,18 @@ public class EdgeEventServiceTest extends AbstractServiceTest { @Before public void before() throws ParseException { - timeBeforeStartTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T11:30:00Z").getTime(); - startTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:00:00Z").getTime(); - eventTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:30:00Z").getTime(); - endTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:00:00Z").getTime(); - timeAfterEndTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:30:30Z").getTime(); + timeBeforeStartTime = ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2016-11-01T11:30:00").getTime(); + startTime = ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2016-11-01T12:00:00").getTime(); + eventTime = ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2016-11-01T12:30:00").getTime(); + endTime = ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2016-11-01T13:00:00").getTime(); + timeAfterEndTime = ISO_8601_EXTENDED_DATETIME_FORMAT.parse("2016-11-01T13:30:30").getTime(); } @Test public void saveEdgeEvent() throws Exception { EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); - TenantId tenantId = new TenantId(Uuids.timeBased()); - EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.ADDED); + EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, deviceId); edgeEventService.saveAsync(edgeEvent).get(); PageData edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, 0L, null, new TimePageLink(1)); @@ -81,9 +80,11 @@ public class EdgeEventServiceTest extends AbstractServiceTest { Assert.assertEquals(saved.getType(), edgeEvent.getType()); Assert.assertEquals(saved.getAction(), edgeEvent.getAction()); Assert.assertEquals(saved.getBody(), edgeEvent.getBody()); + + edgeEventService.cleanupEvents(1); } - protected EdgeEvent generateEdgeEvent(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType edgeEventAction) throws IOException { + protected EdgeEvent generateEdgeEvent(TenantId tenantId, EdgeId edgeId, EntityId entityId) throws IOException { if (tenantId == null) { tenantId = TenantId.fromUUID(Uuids.timeBased()); } @@ -92,7 +93,7 @@ public class EdgeEventServiceTest extends AbstractServiceTest { edgeEvent.setEdgeId(edgeId); edgeEvent.setEntityId(entityId.getId()); edgeEvent.setType(EdgeEventType.DEVICE); - edgeEvent.setAction(edgeEventAction); + edgeEvent.setAction(EdgeEventActionType.ADDED); edgeEvent.setBody(readFromResource("TestJsonData.json")); return edgeEvent; } @@ -101,7 +102,6 @@ public class EdgeEventServiceTest extends AbstractServiceTest { public void findEdgeEventsByTimeDescOrder() throws Exception { EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); - TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); List> futures = new ArrayList<>(); futures.add(saveEdgeEventWithProvidedTime(timeBeforeStartTime, edgeId, deviceId, tenantId)); @@ -133,7 +133,7 @@ public class EdgeEventServiceTest extends AbstractServiceTest { } private ListenableFuture saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { - EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, EdgeEventActionType.ADDED); + EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId); edgeEvent.setId(new EdgeEventId(Uuids.startOf(time))); return edgeEventService.saveAsync(edgeEvent); } diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 75495abec7..ff2bae3a26 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -407,6 +407,53 @@ + + {{ 'notification.edge-trigger-settings' | translate }} +

+
+
+ notification.filter + + + + + alarm.alarm-severity-list + + + {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + + + +
+ + notification.notify-on + + + {{ alarmActionTranslationMap.get(alarmAction) | translate }} + + + + {{ 'notification.notify-on-required' | translate }} + + +
+ +
+
+ + notification.description + + +
+
+ diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index 9604670757..a0569e5359 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -98,6 +98,7 @@ export class RuleNotificationDialogComponent extends apiUsageLimitTemplateForm: FormGroup; newPlatformVersionTemplateForm: FormGroup; rateLimitsTemplateForm: FormGroup; + edgeTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[]; @@ -221,6 +222,12 @@ export class RuleNotificationDialogComponent extends } }); + this.edgeTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + }) + } + ); + this.alarmTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ alarmTypes: [null], diff --git a/ui-ngx/src/app/shared/models/limited-api.models.ts b/ui-ngx/src/app/shared/models/limited-api.models.ts index 714441f0b2..d0e2e0cb76 100644 --- a/ui-ngx/src/app/shared/models/limited-api.models.ts +++ b/ui-ngx/src/app/shared/models/limited-api.models.ts @@ -24,7 +24,9 @@ export enum LimitedApi { WS_UPDATES_PER_SESSION = 'WS_UPDATES_PER_SESSION', CASSANDRA_QUERIES = 'CASSANDRA_QUERIES', TRANSPORT_MESSAGES_PER_TENANT = 'TRANSPORT_MESSAGES_PER_TENANT', - TRANSPORT_MESSAGES_PER_DEVICE = 'TRANSPORT_MESSAGES_PER_DEVICE' + TRANSPORT_MESSAGES_PER_DEVICE = 'TRANSPORT_MESSAGES_PER_DEVICE', + EDGE_EVENTS = 'EDGE_EVENTS', + EDGE_EVENTS_PER_EDGE = 'EDGE_EVENTS_PER_EDGE' } export const LimitedApiTranslationMap = new Map( @@ -38,6 +40,8 @@ export const LimitedApiTranslationMap = new Map( [LimitedApi.WS_UPDATES_PER_SESSION, 'api-limit.ws-updates-per-session'], [LimitedApi.CASSANDRA_QUERIES, 'api-limit.cassandra-queries'], [LimitedApi.TRANSPORT_MESSAGES_PER_TENANT, 'api-limit.transport-messages'], - [LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'] + [LimitedApi.TRANSPORT_MESSAGES_PER_DEVICE, 'api-limit.transport-messages-per-device'], + [LimitedApi.EDGE_EVENTS, 'api-limit.edge-events'], + [LimitedApi.EDGE_EVENTS_PER_EDGE, 'api-limit.edge-events-per-edge'], ] ); diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 9943bebf3d..d23e6c5f84 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -474,7 +474,9 @@ export enum NotificationType { API_USAGE_LIMIT = 'API_USAGE_LIMIT', NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION', RULE_NODE = 'RULE_NODE', - RATE_LIMITS = 'RATE_LIMITS' + RATE_LIMITS = 'RATE_LIMITS', + EDGE_CONNECTIVITY = 'EDGE_CONNECTIVITY', + EDGE_FAILURE = 'EDGE_FAILURE' } export const NotificationTypeIcons = new Map([ @@ -585,6 +587,18 @@ export const NotificationTemplateTypeTranslateMap = new Map([ @@ -612,6 +628,8 @@ export const TriggerTypeTranslationMap = new Map([ [TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'], [TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'], [TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'], + [TriggerType.EDGE_CONNECTIVITY, 'notification.trigger.edge-connectivity'], + [TriggerType.EDGE_FAILURE, 'notification.trigger.edge-failure'] ]); export interface NotificationUserSettings { diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_connected.md b/ui-ngx/src/assets/help/en_US/notification/edge_connected.md new file mode 100644 index 0000000000..2044ecf708 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/edge_connected.md @@ -0,0 +1,57 @@ +#### Edge notification templatization + +
+
+ +Notification subject and message fields support templatization. +The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + +* `edgeId` - the edge id as uuid string; +* `edgeName` - the name of the edge; +* `eventType` - the string representation of the connectivity status: connected or disconnected; + +Parameter names must be wrapped using `${...}`. For example: `${edgeName}`. +You may also modify the value of the parameter with one of the suffixes: + +* `upperCase`, for example - `${edgeName:upperCase}` +* `lowerCase`, for example - `${edgeName:lowerCase}` +* `capitalize`, for example - `${edgeName:capitalize}` + +
+ +##### Examples + +Let's assume the notification about the connecting Edge into the ThingsBoard. +The following template: + +```text +Edge '${edgeName}' is ${eventType} +{:copy-code} +``` + +will be transformed to: + +```text +Edge 'DatacenterEdge' is connected +``` + +
+ +The following template: + +```text +"Edge '${edgeName}' is now ${eventType}" +{:copy-code} +``` + +will be transformed to: + +```text +Edge 'DatacenterEdge' is now connected +``` + +
+
diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_failure.md b/ui-ngx/src/assets/help/en_US/notification/edge_failure.md new file mode 100644 index 0000000000..d0aaba21a0 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/edge_failure.md @@ -0,0 +1,57 @@ +#### Edge notification templatization + +
+
+ +Notification subject and message fields support templatization. +The list of available templatization parameters depends on the template type. +See the available types and parameters below: + +Available template parameters: + +* `edgeId` - the edge id as uuid string; +* `edgeName` - the name of the edge; +* `errorMsg` - the string representation of the error, occurred on the Edge; + +Parameter names must be wrapped using `${...}`. For example: `${edgeName}`. +You may also modify the value of the parameter with one of the suffixes: + +* `upperCase`, for example - `${edgeName:upperCase}` +* `lowerCase`, for example - `${edgeName:lowerCase}` +* `capitalize`, for example - `${edgeName:capitalize}` + +
+ +##### Examples + +Let's assume the notification about the failing of processing connection to Edge. +The following template: + +```text +Edge '${edgeName}' received error +{:copy-code} +``` + +will be transformed to: + +```text +Edge 'DatacenterEdge' received error +``` + +
+ +The following template: + +```text +Error message: '${errorMsg}' +{:copy-code} +``` + +will be transformed to: + +```text +Error message: 'Failed to process edge connection!' +``` + +
+
diff --git a/ui-ngx/src/assets/help/en_US/notification/rate_limits.md b/ui-ngx/src/assets/help/en_US/notification/rate_limits.md index 8f6319ad80..d314e681c2 100644 --- a/ui-ngx/src/assets/help/en_US/notification/rate_limits.md +++ b/ui-ngx/src/assets/help/en_US/notification/rate_limits.md @@ -11,7 +11,7 @@ Available template parameters: * `api` - rate-limited API label; one of: 'REST API requests', 'REST API requests per customer', 'transport messages', 'transport messages per device', 'Cassandra queries', 'WS updates per session', 'notification requests', 'notification requests per rule', - 'entity version creation', 'entity version load'; + 'entity version creation', 'entity version load', 'Edge events', 'Edge events per edge'; * `limitLevelEntityType` - entity type of the limit level entity, e.g. 'Tenant', 'Device', 'Notification rule', 'Customer', etc.; * `limitLevelEntityId` - id of the limit level entity; * `limitLevelEntityName` - name of the limit level entity; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9140f0071a..df0bb635cd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -851,7 +851,9 @@ "rest-api-requests-per-customer": "REST API requests per customer", "transport-messages": "Transport messages", "transport-messages-per-device": "Transport messages per device", - "ws-updates-per-session": "WS updates per session" + "ws-updates-per-session": "WS updates per session", + "edge-events": "Edge events", + "edge-events-per-edge": "Edge events per edge" }, "audit-log": { "audit": "Audit", @@ -3279,6 +3281,7 @@ "device-list-rule-hint": "If the field is empty, the trigger will be applied to all devices", "device-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all device profiles", "disabled": "Disabled", + "edge-trigger-settings": "Edge trigger settings", "edit-notification-recipients-group": "Edit notification recipients group", "edit-notification-template": "Edit notification template", "edit-rule": "Edit rule", @@ -3423,7 +3426,8 @@ "rule-engine-lifecycle-event": "Rule engine lifecycle event", "rule-node": "Rule node", "new-platform-version": "New platform version", - "rate-limits": "Exceeded rate limits" + "rate-limits": "Exceeded rate limits", + "edge": "Edge" }, "templates": "Templates", "notification-templates": "Notifications / Templates", @@ -3444,6 +3448,8 @@ "rule-engine-lifecycle-event": "Rule engine lifecycle event", "new-platform-version": "New platform version", "rate-limits": "Exceeded rate limits", + "edge-connectivity": "Edge connectivity", + "edge-failure": "Edge failure", "trigger": "Trigger", "trigger-required": "Trigger is required" }, From bf0cc22223b94fb0b73c55eba03a0237ca16eae4 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 19 Jan 2024 17:33:04 +0200 Subject: [PATCH 025/137] Init all actors for isolated rule engines --- .../server/actors/app/AppActor.java | 3 +-- .../server/actors/tenant/TenantActor.java | 20 +++++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 98e0bf7c25..fcd6d7a1f9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -202,8 +202,7 @@ public class AppActor extends ContextAwareActor { return Optional.ofNullable(ctx.getOrCreateChildActor(new TbEntityActorId(tenantId), () -> DefaultActorService.TENANT_DISPATCHER_NAME, () -> new TenantActor.ActorCreator(systemContext, tenantId), - () -> systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE) || - systemContext.getPartitionService().isManagedByCurrentService(tenantId))); + () -> true)); } private void onToEdgeSessionMsg(EdgeSessionMsg msg) { diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 01b01a7d4a..04c0f1c9e9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -90,18 +90,16 @@ public class TenantActor extends RuleChainManagerActor { isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); if (isRuleEngine) { - if (systemContext.getPartitionService().isManagedByCurrentService(tenantId)) { - try { - if (getApiUsageState().isReExecEnabled()) { - log.debug("[{}] Going to init rule chains", tenantId); - initRuleChains(); - } else { - log.info("[{}] Skip init of the rule chains due to API limits", tenantId); - } - } catch (Exception e) { - log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e); - cantFindTenant = true; + try { + if (getApiUsageState().isReExecEnabled()) { + log.debug("[{}] Going to init rule chains", tenantId); + initRuleChains(); + } else { + log.info("[{}] Skip init of the rule chains due to API limits", tenantId); } + } catch (Exception e) { + log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e); + cantFindTenant = true; } } log.debug("[{}] Tenant actor started.", tenantId); From b87699dc8d463b0e9a41fd84cddb88420f2080ac Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 23 Jan 2024 11:20:36 +0200 Subject: [PATCH 026/137] UI: Fixed progress bar --- .../vc/repository-settings.component.html | 2 +- .../vc/repository-settings.component.ts | 3 +++ .../vc/version-control.component.html | 6 ++++-- ui-ngx/src/app/modules/home/home.component.ts | 20 ++++++++++++++++--- .../home/pages/admin/admin-routing.module.ts | 3 +++ .../notification-routing.module.ts | 1 + .../widget/widget-library-routing.module.ts | 1 + 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index 10fae28b6d..e0d33fdfd5 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -26,7 +26,7 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts index e65658ea53..b2af171353 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts @@ -48,6 +48,9 @@ export class RepositorySettingsComponent extends PageComponent implements OnInit @Input() popoverComponent: TbPopoverComponent; + @Input() + hideLoadingBar = false; + repositorySettingsForm: UntypedFormGroup; settings: RepositorySettings = null; diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html index 4495ce5c5e..566c1b149f 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html @@ -15,9 +15,11 @@ limitations under the License. --> - + *ngIf="!(hasRepository$ | async); else versionsTable"> , @Inject(WINDOW) private window: Window, private activeComponentService: ActiveComponentService, - public breakpointObserver: BreakpointObserver, - private fb: FormBuilder) { + private fb: FormBuilder, + private route: ActivatedRoute, + public breakpointObserver: BreakpointObserver) { super(store); } @@ -144,9 +147,20 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni private updateActiveComponent(activeComponent: any) { this.showSearch = false; + this.hideLoadingBar = false; this.textSearch.reset('', {emitEvent: false}); this.activeComponent = activeComponent; - this.hideLoadingBar = activeComponent && activeComponent instanceof RouterTabsComponent; + let showLoadingBar: boolean; + if (isDefined(this.route.children[0]?.snapshot?.data?.showLoadingBar)) { + showLoadingBar = this.route.children[0]?.snapshot?.data?.showLoadingBar; + } else if (isDefined(this.route.children[0]?.children[0]?.snapshot?.data?.showLoadingBar)) { + showLoadingBar = this.route.children[0]?.children[0]?.snapshot?.data?.showLoadingBar; + } + if (activeComponent && activeComponent instanceof RouterTabsComponent) { + this.hideLoadingBar = isDefinedAndNotNull(showLoadingBar) ? !showLoadingBar : true; + } else if (isDefinedAndNotNull(showLoadingBar)) { + this.hideLoadingBar = !showLoadingBar; + } if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) { this.searchEnabled = true; this.searchableComponent = this.activeComponent; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 74c5012791..c91448c7d7 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -323,6 +323,7 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], + showLoadingBar: false, title: 'admin.general', breadcrumb: { label: 'admin.general', @@ -336,6 +337,7 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], + showLoadingBar: false, title: 'admin.2fa.2fa', breadcrumb: { label: 'admin.2fa.2fa', @@ -349,6 +351,7 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], + showLoadingBar: false, title: 'admin.oauth2.oauth2', breadcrumb: { label: 'admin.oauth2.oauth2', diff --git a/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts index 41f49ee708..995bda9234 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/notification-routing.module.ts @@ -32,6 +32,7 @@ const routes: Routes = [ component: RouterTabsComponent, data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], + showLoadingBar: true, breadcrumb: { label: 'notification.notification-center', icon: 'mdi:message-badge' diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index c808bae163..160a0e18da 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -234,6 +234,7 @@ export const widgetsLibraryRoutes: Routes = [ component: RouterTabsComponent, data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + showLoadingBar: true, breadcrumb: { label: 'widget.widget-library', icon: 'now_widgets' From d98a8cd2fa320b676a02b99190c277e21e88bc5d Mon Sep 17 00:00:00 2001 From: deaflynx Date: Tue, 23 Jan 2024 13:47:20 +0200 Subject: [PATCH 027/137] Update notification and rate limit configurations for edge connectivity/failure events --- .../EdgeConnectivityTriggerProcessor.java | 2 +- ...ectivityNotificationRuleTriggerConfig.java | 1 - ...enant-profile-configuration.component.html | 18 +++-- ...-tenant-profile-configuration.component.ts | 4 +- .../tenant/rate-limits/rate-limits.models.ts | 8 ++- .../rule-notification-dialog.component.html | 66 +++++++++++-------- .../rule-notification-dialog.component.ts | 24 +++++-- ui-ngx/src/app/shared/models/edge.models.ts | 12 ++++ ...edge_connected.md => edge_connectivity.md} | 0 .../assets/locale/locale.constant-en_US.json | 12 +++- 10 files changed, 105 insertions(+), 42 deletions(-) rename ui-ngx/src/assets/help/en_US/notification/{edge_connected.md => edge_connectivity.md} (100%) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java index 7133bd2d76..b5418f6abb 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java @@ -32,7 +32,7 @@ public class EdgeConnectivityTriggerProcessor implements NotificationRuleTrigger @Override public boolean matchesFilter(EdgeConnectivityTrigger trigger, EdgeConnectivityNotificationRuleTriggerConfig triggerConfig) { EdgeConnectivityEvent event = trigger.isConnected() ? EdgeConnectivityEvent.CONNECTED : EdgeConnectivityEvent.DISCONNECTED; - if (!triggerConfig.getNotifyOn().contains(event)) { + if (CollectionUtils.isEmpty(triggerConfig.getNotifyOn()) || !triggerConfig.getNotifyOn().contains(event)) { return false; } if (CollectionUtils.isNotEmpty(triggerConfig.getEdges())) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java index bf22487614..e79f9b0968 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java @@ -31,7 +31,6 @@ import java.util.UUID; public class EdgeConnectivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { private Set edges; // if empty - all edges - @NotEmpty private Set notifyOn; @Override diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 4c309f5e80..867cd74f3b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -523,7 +523,7 @@ -
+
@@ -531,7 +531,7 @@ [type]="rateLimitsType.DEVICE_TELEMETRY_DATA_POINTS">
-
+
@@ -539,7 +539,7 @@ [type]="rateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION">
-
+
@@ -547,7 +547,7 @@ [type]="rateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT">
-
+
@@ -555,7 +555,7 @@ [type]="rateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION">
-
+
@@ -563,6 +563,14 @@ [type]="rateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT">
+
+ + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index a0b8770509..0c95324d85 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -106,7 +106,9 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxWsSubscriptionsPerRegularUser: [null, [Validators.min(0)]], maxWsSubscriptionsPerPublicUser: [null, [Validators.min(0)]], wsUpdatesPerSessionRateLimit: [null, []], - cassandraQueryTenantRateLimitsConfiguration: [null, []] + cassandraQueryTenantRateLimitsConfiguration: [null, []], + edgeEventRateLimits: [null, []], + edgeEventRateLimitsPerEdge: [null, []] }); this.defaultTenantProfileConfigurationFormGroup.get('smsEnabled').valueChanges.pipe( diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts index 257752f5f4..8924682aa1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts @@ -35,7 +35,9 @@ export enum RateLimitsType { TENANT_ENTITY_EXPORT_RATE_LIMIT = 'TENANT_ENTITY_EXPORT_RATE_LIMIT', TENANT_ENTITY_IMPORT_RATE_LIMIT = 'TENANT_ENTITY_IMPORT_RATE_LIMIT', TENANT_NOTIFICATION_REQUEST_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUEST_RATE_LIMIT', - TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT' + TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT = 'TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT', + EDGE_EVENTS_RATE_LIMIT = 'EDGE_EVENTS_RATE_LIMIT', + EDGE_EVENTS_PER_EDGE_RATE_LIMIT = 'EDGE_EVENTS_PER_EDGE_RATE_LIMIT' } export const rateLimitsLabelTranslationMap = new Map( @@ -54,6 +56,8 @@ export const rateLimitsLabelTranslationMap = new Map( [RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-import-rate-limit'], [RateLimitsType.TENANT_NOTIFICATION_REQUEST_RATE_LIMIT, 'tenant-profile.tenant-notification-request-rate-limit'], [RateLimitsType.TENANT_NOTIFICATION_REQUESTS_PER_RULE_RATE_LIMIT, 'tenant-profile.tenant-notification-requests-per-rule-rate-limit'], + [RateLimitsType.EDGE_EVENTS_RATE_LIMIT, 'tenant-profile.rate-limits.edge-events-rate-limit'], + [RateLimitsType.EDGE_EVENTS_PER_EDGE_RATE_LIMIT, 'tenant-profile.rate-limits.edge-events-per-edge-rate-limit'], ] ); @@ -73,6 +77,8 @@ export const rateLimitsDialogTitleTranslationMap = new Map - + + {{ 'notification.edge-trigger-settings' | translate }} -
+
notification.filter - - - + + - alarm.alarm-severity-list - - - {{ alarmSeverityTranslationMap.get(alarmSeverity) | translate }} + notification.notify-on + + + {{ edgeConnectivityEventTranslationMap.get(edgeEvent) | translate }}
- - notification.notify-on - - - {{ alarmActionTranslationMap.get(alarmAction) | translate }} - - - - {{ 'notification.notify-on-required' | translate }} - +
+
+
+
+ + notification.description +
+
+ + + {{ 'notification.edge-trigger-settings' | translate }} +
+
+
+ notification.filter + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index a0569e5359..099897af7c 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -68,6 +68,7 @@ import { } from '@shared/models/api-usage.models'; import { LimitedApi, LimitedApiTranslationMap } from '@shared/models/limited-api.models'; import { StringItemsOption } from '@shared/components/string-items-list.component'; +import { EdgeConnectivityEvent, EdgeConnectivityEventTranslationMap } from '@shared/models/edge.models'; export interface RuleNotificationDialogData { rule?: NotificationRule; @@ -98,7 +99,8 @@ export class RuleNotificationDialogComponent extends apiUsageLimitTemplateForm: FormGroup; newPlatformVersionTemplateForm: FormGroup; rateLimitsTemplateForm: FormGroup; - edgeTemplateForm: FormGroup; + edgeFailureTemplateForm: FormGroup; + edgeConnectivityTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[]; @@ -133,6 +135,9 @@ export class RuleNotificationDialogComponent extends apiFeatures: ApiFeature[] = Object.values(ApiFeature); apiFeatureTranslationMap = ApiFeatureTranslationMap; + edgeConnectivityEvents: EdgeConnectivityEvent[] = Object.values(EdgeConnectivityEvent); + edgeConnectivityEventTranslationMap = EdgeConnectivityEventTranslationMap; + limitedApis: StringItemsOption[]; entityType = EntityType; @@ -222,11 +227,18 @@ export class RuleNotificationDialogComponent extends } }); - this.edgeTemplateForm = this.fb.group({ + this.edgeConnectivityTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ + edges: [null], + notifyOn: [null] }) - } - ); + }); + + this.edgeFailureTemplateForm = this.fb.group({ + triggerConfig: this.fb.group({ + edges: [null] + }) + }); this.alarmTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ @@ -335,7 +347,9 @@ export class RuleNotificationDialogComponent extends [TriggerType.ENTITIES_LIMIT, this.entitiesLimitTemplateForm], [TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm], [TriggerType.NEW_PLATFORM_VERSION, this.newPlatformVersionTemplateForm], - [TriggerType.RATE_LIMITS, this.rateLimitsTemplateForm] + [TriggerType.RATE_LIMITS, this.rateLimitsTemplateForm], + [TriggerType.EDGE_FAILURE, this.edgeFailureTemplateForm], + [TriggerType.EDGE_CONNECTIVITY, this.edgeConnectivityTemplateForm] ]); if (data.isAdd || data.isCopy) { diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 7187dc8eaa..4da5f089a0 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -190,3 +190,15 @@ export enum EdgeInstructionsMethod { } export const edgeVersionAttributeKey = 'edgeVersion'; + +export enum EdgeConnectivityEvent { + CONNECTED= 'CONNECTED', + DISCONNECTED = 'DISCONNECTED' +} + +export const EdgeConnectivityEventTranslationMap = new Map( + [ + [EdgeConnectivityEvent.CONNECTED, 'edge-event.connected'], + [EdgeConnectivityEvent.DISCONNECTED, 'edge-event.disconnected'] + ] +); diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_connected.md b/ui-ngx/src/assets/help/en_US/notification/edge_connectivity.md similarity index 100% rename from ui-ngx/src/assets/help/en_US/notification/edge_connected.md rename to ui-ngx/src/assets/help/en_US/notification/edge_connectivity.md diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index df0bb635cd..63b791a6ef 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2073,7 +2073,9 @@ "action-type-assigned-to-edge": "Assigned to Edge", "action-type-unassigned-from-edge": "Unassigned from Edge", "action-type-credentials-request": "Credentials Request", - "action-type-entity-merge-request": "Entity Merge Request" + "action-type-entity-merge-request": "Entity Merge Request", + "connected": "Connected", + "disconnected": "Disconnected" }, "error": { "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", @@ -3282,6 +3284,7 @@ "device-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all device profiles", "disabled": "Disabled", "edge-trigger-settings": "Edge trigger settings", + "edge-list-rule-hint": "If the field is empty, the trigger will be applied to all edge instances", "edit-notification-recipients-group": "Edit notification recipients group", "edit-notification-template": "Edit notification template", "edit-rule": "Edit rule", @@ -3427,7 +3430,8 @@ "rule-node": "Rule node", "new-platform-version": "New platform version", "rate-limits": "Exceeded rate limits", - "edge": "Edge" + "edge-failure": "Edge error", + "edge-connectivity": "Edge connectivity" }, "templates": "Templates", "notification-templates": "Notifications / Templates", @@ -4173,6 +4177,10 @@ "edit-tenant-entity-import-rate-limit-title": "Edit entity version load rate limits", "edit-tenant-notification-request-rate-limit-title": "Edit notification requests rate limits", "edit-tenant-notification-requests-per-rule-rate-limit-title": "Edit notification requests per notification rule rate limits", + "edit-edge-events-rate-limit": "Edit edge events rate limits", + "edit-edge-events-per-edge-rate-limit": "Edit edge events per edge rate limits", + "edge-events-rate-limit": "Edge events", + "edge-events-per-edge-rate-limit": "Edge events per edge", "messages-per": "messages per", "not-set": "Not set", "number-of-messages": "Number of messages", From 2d6dba7772207e46dbcc6291eb468c430467aece Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 23 Jan 2024 15:27:53 +0200 Subject: [PATCH 028/137] UI: Refactoring --- .../vc/repository-settings.component.html | 2 +- .../vc/repository-settings.component.ts | 2 ++ .../vc/version-control.component.html | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 10 +++++----- .../home/pages/admin/admin-routing.module.ts | 3 --- .../pages/admin/oauth2-settings.component.ts | 20 +++++++++++++++---- .../admin/security-settings.component.ts | 13 ++++++------ .../two-factor-auth-settings.component.ts | 5 ++++- .../modules/home/pages/home-pages.models.ts | 4 ++++ 9 files changed, 40 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index e0d33fdfd5..7ba277364d 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -28,7 +28,7 @@ -
+
diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts index b2af171353..2cdef0dea4 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts @@ -34,6 +34,7 @@ import { selectHasRepository } from '@core/auth/auth.selectors'; import { catchError, mergeMap, take } from 'rxjs/operators'; import { of } from 'rxjs'; import { TbPopoverComponent } from '@shared/components/popover.component'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-repository-settings', @@ -49,6 +50,7 @@ export class RepositorySettingsComponent extends PageComponent implements OnInit popoverComponent: TbPopoverComponent; @Input() + @coerceBoolean() hideLoadingBar = false; repositorySettingsForm: UntypedFormGroup; diff --git a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html index 566c1b149f..eb4b1d962b 100644 --- a/ui-ngx/src/app/modules/home/components/vc/version-control.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/version-control.component.html @@ -17,7 +17,7 @@ --> diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 3042061bb5..f88cdc9dec 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -15,7 +15,7 @@ /// import { AfterViewInit, Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { startWith, skip, Subject } from 'rxjs'; +import { skip, startWith, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; @@ -151,10 +151,10 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni this.textSearch.reset('', {emitEvent: false}); this.activeComponent = activeComponent; let showLoadingBar: boolean; - if (isDefined(this.route.children[0]?.snapshot?.data?.showLoadingBar)) { - showLoadingBar = this.route.children[0]?.snapshot?.data?.showLoadingBar; - } else if (isDefined(this.route.children[0]?.children[0]?.snapshot?.data?.showLoadingBar)) { - showLoadingBar = this.route.children[0]?.children[0]?.snapshot?.data?.showLoadingBar; + if (isDefined(this.activeComponent.activatedRoute?.data?.value?.showLoadingBar)) { + showLoadingBar = this.activeComponent.activatedRoute?.data?.value?.showLoadingBar; + } else if (isDefined(this.activeComponent?.showLoadingBar)) { + showLoadingBar = this.activeComponent.showLoadingBar; } if (activeComponent && activeComponent instanceof RouterTabsComponent) { this.hideLoadingBar = isDefinedAndNotNull(showLoadingBar) ? !showLoadingBar : true; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index c91448c7d7..74c5012791 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -323,7 +323,6 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], - showLoadingBar: false, title: 'admin.general', breadcrumb: { label: 'admin.general', @@ -337,7 +336,6 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], - showLoadingBar: false, title: 'admin.2fa.2fa', breadcrumb: { label: 'admin.2fa.2fa', @@ -351,7 +349,6 @@ const routes: Routes = [ canDeactivate: [ConfirmOnExitGuard], data: { auth: [Authority.SYS_ADMIN], - showLoadingBar: false, title: 'admin.oauth2.oauth2', breadcrumb: { label: 'admin.oauth2.oauth2', diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 4b0ba741d3..798b07c461 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -15,7 +15,14 @@ /// import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; import { ClientAuthenticationMethod, DomainSchema, @@ -26,9 +33,11 @@ import { MapperConfigType, OAuth2ClientRegistrationTemplate, OAuth2DomainInfo, - OAuth2Info, OAuth2MobileInfo, + OAuth2Info, + OAuth2MobileInfo, OAuth2ParamsInfo, - OAuth2RegistrationInfo, PlatformType, + OAuth2RegistrationInfo, + PlatformType, platformTypeTranslations, TenantNameStrategy } from '@shared/models/oauth2.models'; @@ -45,13 +54,14 @@ import { TranslateService } from '@ngx-translate/core'; import { isDefined, isDefinedAndNotNull, randomAlphanumeric } from '@core/utils'; import { OAuth2Service } from '@core/http/oauth2.service'; import { ActivatedRoute } from '@angular/router'; +import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-oauth2-settings', templateUrl: './oauth2-settings.component.html', styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] }) -export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, HasShowLoading, OnDestroy { constructor(protected store: Store, private route: ActivatedRoute, @@ -105,6 +115,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; + showLoadingBar = false; + private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; private static validateScope(control: AbstractControl): ValidationErrors | null { diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts index e4fd483eb9..f9449eba33 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -23,7 +23,8 @@ import { AbstractControl, UntypedFormBuilder, UntypedFormControl, - UntypedFormGroup, ValidationErrors, + UntypedFormGroup, + ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; @@ -35,21 +36,21 @@ import { randomAlphanumeric } from '@core/utils'; import { AuthService } from '@core/auth/auth.service'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; -import { forkJoin, Observable, of } from 'rxjs'; -import { MatCheckboxChange } from '@angular/material/checkbox'; -import { AlarmInfo } from '@shared/models/alarm.models'; -import { QueueProcessingStrategyTypes, QueueProcessingStrategyTypesMap } from '@shared/models/queue.models'; +import { Observable, of } from 'rxjs'; +import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-security-settings', templateUrl: './security-settings.component.html', styleUrls: ['./security-settings.component.scss', './settings-card.scss'] }) -export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm { +export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm, HasShowLoading { securitySettingsFormGroup: UntypedFormGroup; jwtSecuritySettingsFormGroup: UntypedFormGroup; + showLoadingBar = false; + private securitySettings: SecuritySettings; private jwtSettings: JwtSettings; diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index 5ea4776ff9..208d133e3e 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -32,13 +32,14 @@ import { isNotEmptyStr } from '@core/utils'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { MatExpansionPanel } from '@angular/material/expansion'; +import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-2fa-settings', templateUrl: './two-factor-auth-settings.component.html', styleUrls: [ './settings-card.scss', './two-factor-auth-settings.component.scss'] }) -export class TwoFactorAuthSettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { +export class TwoFactorAuthSettingsComponent extends PageComponent implements OnInit, HasConfirmForm, HasShowLoading, OnDestroy { private readonly destroy$ = new Subject(); private readonly posIntValidation = [Validators.required, Validators.min(1), Validators.pattern(/^\d*$/)]; @@ -47,6 +48,8 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI twoFactorAuthProviderType = TwoFactorAuthProviderType; twoFactorAuthProvidersData = twoFactorAuthProvidersData; + showLoadingBar = false; + @ViewChildren(MatExpansionPanel) expansionPanel: QueryList; constructor(protected store: Store, diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts index 20872e3c65..456bbb6999 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts @@ -31,3 +31,7 @@ export const entityDetailsPageBreadcrumbLabelFunction: BreadCrumbLabelFunction Date: Tue, 23 Jan 2024 15:41:02 +0200 Subject: [PATCH 029/137] UI: Remove route --- ui-ngx/src/app/modules/home/home.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index f88cdc9dec..a314803e9c 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -73,7 +73,6 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni @Inject(WINDOW) private window: Window, private activeComponentService: ActiveComponentService, private fb: FormBuilder, - private route: ActivatedRoute, public breakpointObserver: BreakpointObserver) { super(store); } From c9d97553b8761c8a4a26822958c21c81364888b1 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 11:10:17 +0200 Subject: [PATCH 030/137] Update license headers with 2024 year. --- .../server/service/queue/DefaultTbCoreConsumerServiceTest.java | 2 +- .../thingsboard/server/queue/common/SimpleTbQueueCallback.java | 2 +- .../org/thingsboard/rule/engine/action/TbDeviceStateNode.java | 2 +- .../rule/engine/action/TbDeviceStateNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/action/TbDeviceStateNodeTest.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java index 3ac86dac0c..fd7e2077d4 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * 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. diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java index 805511a603..5f86060d48 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index c3593d6cc7..63ebf09b9c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java index c89fcb076f..c23338fcd1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * 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. diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index cb81b4d2e4..246e5b3459 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2023 The Thingsboard Authors + * 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. From e275678c22e4ba89eaca353117c140150fbdb016 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 12:55:09 +0200 Subject: [PATCH 031/137] Add check for negative and outdated inactivity event timestamp. --- .../state/DefaultDeviceStateService.java | 14 +++++- .../state/DefaultDeviceStateServiceTest.java | 49 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 458412b437..ee7b4a7e72 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -297,8 +297,20 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService givenOutdatedLastInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent() { + return Stream.of( + Arguments.of(0, 0), + Arguments.of(0, 100), + Arguments.of(50, 100), + Arguments.of(99, 100), + Arguments.of(100, 100) + ); + } + @Test public void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() { // GIVEN From ab21a1395b9554178dda383107231dbf75055ae3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 13:06:40 +0200 Subject: [PATCH 032/137] Remove unnecessary times(1) in test verifications --- .../state/DefaultDeviceStateServiceTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 816043c4d4..c924e6d93c 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -76,7 +76,6 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @@ -135,7 +134,7 @@ public class DefaultDeviceStateServiceTest { service.onDeviceInactivity(tenantId, deviceId, System.currentTimeMillis()); // THEN - then(service).should(times(1)).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); + then(service).should().cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); then(service).should(never()).fetchDeviceStateDataUsingSeparateRequests(deviceId); then(clusterService).shouldHaveNoInteractions(); then(notificationRuleProcessor).shouldHaveNoInteractions(); @@ -209,24 +208,24 @@ public class DefaultDeviceStateServiceTest { service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime); // THEN - then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + then(telemetrySubscriptionService).should().saveAttrAndNotify( eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any() ); - then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + then(telemetrySubscriptionService).should().saveAttrAndNotify( eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - then(clusterService).should(times(1)) + then(clusterService).should() .pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); var actualMsg = msgCaptor.getValue(); assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); - then(notificationRuleProcessor).should(times(1)).process(notificationCaptor.capture()); + then(notificationRuleProcessor).should().process(notificationCaptor.capture()); var actualNotification = notificationCaptor.getValue(); assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); @@ -249,24 +248,24 @@ public class DefaultDeviceStateServiceTest { service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); // THEN - then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + then(telemetrySubscriptionService).should().saveAttrAndNotify( eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), eq(INACTIVITY_ALARM_TIME), anyLong(), any() ); - then(telemetrySubscriptionService).should(times(1)).saveAttrAndNotify( + then(telemetrySubscriptionService).should().saveAttrAndNotify( eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(DataConstants.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() ); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); - then(clusterService).should(times(1)) + then(clusterService).should() .pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); var actualMsg = msgCaptor.getValue(); assertThat(actualMsg.getType()).isEqualTo(TbMsgType.INACTIVITY_EVENT.name()); assertThat(actualMsg.getOriginator()).isEqualTo(deviceId); var notificationCaptor = ArgumentCaptor.forClass(DeviceActivityTrigger.class); - then(notificationRuleProcessor).should(times(1)).process(notificationCaptor.capture()); + then(notificationRuleProcessor).should().process(notificationCaptor.capture()); var actualNotification = notificationCaptor.getValue(); assertThat(actualNotification.getTenantId()).isEqualTo(tenantId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); @@ -287,7 +286,7 @@ public class DefaultDeviceStateServiceTest { willReturn(deviceStateDataMock).given(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); assertThat(deviceStateData).isEqualTo(deviceStateDataMock); - verify(service, times(1)).fetchDeviceStateDataUsingSeparateRequests(deviceId); + verify(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); } @Test @@ -513,7 +512,7 @@ public class DefaultDeviceStateServiceTest { } private void activityVerify(boolean isActive) { - verify(telemetrySubscriptionService, times(1)).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(isActive), any()); + verify(telemetrySubscriptionService).saveAttrAndNotify(any(), eq(deviceId), any(), eq(ACTIVITY_STATE), eq(isActive), any()); } @Test @@ -869,7 +868,7 @@ public class DefaultDeviceStateServiceTest { } } - then(service).should(times(1)).fetchDeviceStateDataUsingSeparateRequests(deviceId); + then(service).should().fetchDeviceStateDataUsingSeparateRequests(deviceId); } } From 1822703730ee2bace9aed2ebc907df8e79dea4c2 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 13:15:33 +0200 Subject: [PATCH 033/137] Add default overloads with current time millis for onDeviceConnect() and onDeviceDisconnect(). --- .../server/actors/device/DeviceActorMessageProcessor.java | 4 ++-- .../server/service/state/DeviceStateService.java | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index c5625ee8ad..58bd82abb3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -490,11 +490,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } private void reportSessionOpen() { - systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId, System.currentTimeMillis()); + systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId); } private void reportSessionClose() { - systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId, System.currentTimeMillis()); + systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId); } private void handleGetAttributesRequest(SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java index 85d5645e1e..3ab5d29194 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java @@ -29,10 +29,18 @@ public interface DeviceStateService extends ApplicationListener Date: Wed, 24 Jan 2024 13:25:02 +0200 Subject: [PATCH 034/137] UI: liquid level widget improvements --- ...uid-level-card-basic-config.component.html | 24 ++++++- ...iquid-level-card-basic-config.component.ts | 6 +- .../liquid-level-widget.component.ts | 63 ++++++++++++++----- .../indicator/liquid-level-widget.models.ts | 31 +++++++-- ...-level-card-widget-settings.component.html | 24 ++++++- ...id-level-card-widget-settings.component.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 1 + 7 files changed, 127 insertions(+), 26 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index d79947185b..4940df18ba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -184,10 +184,30 @@ formControlName="volumeAttributeName"> - +
+
+
widgets.liquid-level-card.total-volume-units
+
+ + + + {{ DataSourceTypeTranslations.get(type) | translate }} + + + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts index b246ed2db1..0fc034d496 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts @@ -194,7 +194,9 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon volumeSource: [settings.volumeSource, []], volumeConstant: [settings.volumeConstant, [Validators.required, Validators.min(0.1)]], volumeAttributeName: [settings.volumeAttributeName, [Validators.required]], + volumeUnitsSource: [settings.volumeUnitsSource, []], volumeUnits: [settings.volumeUnits, [Validators.required]], + volumeUnitsAttributeName: [settings.volumeUnitsAttributeName, [Validators.required]], volumeFont: [settings.volumeFont, []], volumeColor: [settings.volumeColor, []], units: [settings.units, [Validators.required]], @@ -260,6 +262,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon this.widgetConfig.config.settings.volumeSource = config.volumeSource; this.widgetConfig.config.settings.volumeConstant = config.volumeConstant; this.widgetConfig.config.settings.volumeAttributeName = config.volumeAttributeName; + this.widgetConfig.config.settings.volumeUnitsSource = config.volumeUnitsSource; + this.widgetConfig.config.settings.volumeUnitsAttributeName = config.volumeUnitsAttributeName; this.widgetConfig.config.settings.volumeUnits = config.volumeUnits; this.widgetConfig.config.settings.volumeFont = config.volumeFont; this.widgetConfig.config.settings.volumeColor = config.volumeColor; @@ -294,7 +298,7 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon protected validatorTriggers(): string[] { return [ 'showTooltip', 'showTooltipLevel', 'tankSelectionType', 'datasourceUnits', 'showTitleIcon', 'volumeSource', - 'showTooltipDate', 'layout', 'showTitle', 'widgetUnitsSource' + 'showTooltipDate', 'layout', 'showTitle', 'widgetUnitsSource', 'volumeUnitsSource' ]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts index 3c79883bf3..0933bfe537 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts @@ -106,6 +106,7 @@ export class LiquidLevelWidgetComponent implements OnInit { private volume: number; private tooltipContent: string; private widgetUnits: string; + private volumeUnits: string; private capacityUnits = Object.values(CapacityUnits); @@ -128,7 +129,7 @@ export class LiquidLevelWidgetComponent implements OnInit { this.getData().subscribe(data => { if (data) { - const { svg, volume, units } = data; + const { svg, volume, units, volumeUnits } = data; if (svg && isNotEmptyStr(svg) && this.liquidLevelContent.nativeElement) { const jQueryContainerElement = $(this.liquidLevelContent.nativeElement); jQueryContainerElement.html(svg); @@ -145,6 +146,10 @@ export class LiquidLevelWidgetComponent implements OnInit { this.volume = Number(volume); } + if (volumeUnits) { + this.volumeUnits = volumeUnits; + } + if (units) { this.widgetUnits = units; } @@ -164,7 +169,7 @@ export class LiquidLevelWidgetComponent implements OnInit { this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); } - private getData(): Observable<{ svg: string; volume: number; units: string }> { + private getData(): Observable<{ svg: string; volume: number; units: string; volumeUnits: string}> { if (this.ctx.datasources?.length) { const entityId: EntityId = { entityType: this.ctx.datasources[0].entityType, @@ -308,7 +313,7 @@ export class LiquidLevelWidgetComponent implements OnInit { .pipe(map(attributes => { const shape = extractValue(attributes, this.settings.shapeAttributeName); if (!shape || !svgMapping.has(shape)) { - this.createdErrorMgs(this.settings.shapeAttributeName, isUndefinedOrNull(shape) || isEmptyStr(shape)); + this.createdErrorMsg(this.settings.shapeAttributeName, isUndefinedOrNull(shape) || isEmptyStr(shape)); return this.settings.selectedShape; } return shape; @@ -318,12 +323,15 @@ export class LiquidLevelWidgetComponent implements OnInit { return of(this.settings.selectedShape); } - private getTankersParams(entityId: EntityId): Observable<{ volume: number; units: string }> { + private getTankersParams(entityId: EntityId): Observable<{ volume: number; units: string; volumeUnits: string }> { const isVolumeStatic = this.settings.layout !== LevelCardLayout.absolute && this.settings.datasourceUnits === CapacityUnits.percent || this.settings.volumeSource === LiquidWidgetDataSourceType.static; const isUnitStatic = this.settings.layout !== LevelCardLayout.absolute || this.settings.widgetUnitsSource === LiquidWidgetDataSourceType.static; + const isVolumeUnitStatic = this.settings.layout !== LevelCardLayout.absolute + && this.settings.datasourceUnits === CapacityUnits.percent + || this.settings.volumeUnitsSource === LiquidWidgetDataSourceType.static; const attributeKeys: string[] = []; @@ -335,20 +343,29 @@ export class LiquidLevelWidgetComponent implements OnInit { attributeKeys.push(this.settings.widgetUnitsAttributeName); } + if (!isVolumeUnitStatic) { + attributeKeys.push(this.settings.volumeUnitsAttributeName); + } + if (!attributeKeys.length || entityId.id === NULL_UUID) { return of({ volume: this.settings.volumeConstant, + volumeUnits: this.settings.volumeUnits, units: this.settings.units }); } return this.ctx.attributeService.getEntityAttributes(entityId, null, attributeKeys).pipe( map(attributes => { - let volume = isVolumeStatic ? this.settings.volumeConstant : extractValue(attributes, this.settings.volumeAttributeName); - let units = isUnitStatic ? this.settings.units : extractValue(attributes, this.settings.widgetUnitsAttributeName); + let volume = isVolumeStatic ? this.settings.volumeConstant : + extractValue(attributes, this.settings.volumeAttributeName); + let volumeUnits = isVolumeUnitStatic ? this.settings.volumeUnits : + extractValue(attributes, this.settings.volumeUnitsAttributeName); + let units = isUnitStatic ? this.settings.units : + extractValue(attributes, this.settings.widgetUnitsAttributeName); if (!isVolumeStatic && (!volume || !isNumeric(volume) || volume < 0.1)) { - this.createdErrorMgs(this.settings.volumeAttributeName, isUndefinedOrNull(volume) || isEmptyStr(volume)); + this.createdErrorMsg(this.settings.volumeAttributeName, isUndefinedOrNull(volume) || isEmptyStr(volume)); volume = this.settings.volumeConstant; } @@ -358,20 +375,33 @@ export class LiquidLevelWidgetComponent implements OnInit { units = this.capacityUnits.find(unit => unit.normalize() === normalizeUnits); } if (isUndefinedOrNull(units) || !isNotEmptyStr(units)) { - this.createdErrorMgs(this.settings.widgetUnitsAttributeName, isUndefinedOrNull(units) || isEmptyStr(units)); + this.createdErrorMsg(this.settings.widgetUnitsAttributeName, isUndefinedOrNull(units) || isEmptyStr(units)); units = this.settings.units; } } + if (!isVolumeUnitStatic) { + if (isNotEmptyStr(volumeUnits)) { + const normalizeUnits = volumeUnits.normalize().trim(); + volumeUnits = this.capacityUnits.find(unit => unit.normalize() === normalizeUnits); + } + if (isUndefinedOrNull(volumeUnits) || !isNotEmptyStr(volumeUnits)) { + this.createdErrorMsg(this.settings.widgetUnitsAttributeName, + isUndefinedOrNull(volumeUnits) || isEmptyStr(volumeUnits)); + volumeUnits = this.settings.volumeUnits; + } + } + return { volume, + volumeUnits, units }; }) ); } - private createdErrorMgs(attributeName: string, isEmpty = false) { + private createdErrorMsg(attributeName: string, isEmpty = false) { if (isEmpty) { this.errorsMsg.push(this.translate.instant('widgets.liquid-level-card.attribute-key-not-set', {attributeName})); } else { @@ -474,9 +504,14 @@ export class LiquidLevelWidgetComponent implements OnInit { } if (this.settings.layout === LevelCardLayout.absolute) { - const volumeInLiters: number = convertLiters(this.volume, this.settings.volumeUnits as CapacityUnits, ConversionType.to); - const volume = convertLiters(volumeInLiters, this.widgetUnits as CapacityUnits, ConversionType.from) - .toFixed(this.settings.decimals || 0); + let volume: number | string; + if (this.widgetUnits !== CapacityUnits.percent) { + const volumeInLiters: number = convertLiters(this.volume, this.volumeUnits as CapacityUnits, ConversionType.to); + volume = convertLiters(volumeInLiters, this.widgetUnits as CapacityUnits, ConversionType.from) + .toFixed(this.settings.decimals || 0); + } else { + volume = this.volume.toFixed(this.settings.decimals || 0); + } const volumeTextStyle = cssTextFromInlineStyle({...inlineTextStyle(this.settings.volumeFont), color: this.settings.volumeColor}); @@ -553,7 +588,7 @@ export class LiquidLevelWidgetComponent implements OnInit { private convertInputData(value: any): number { if (this.settings.datasourceUnits !== CapacityUnits.percent) { return (convertLiters(Number(value), this.settings.datasourceUnits, ConversionType.to) / - convertLiters(this.volume, this.settings.volumeUnits, ConversionType.to)) * 100; + convertLiters(this.volume, this.volumeUnits as CapacityUnits, ConversionType.to)) * 100; } return Number(value); @@ -561,7 +596,7 @@ export class LiquidLevelWidgetComponent implements OnInit { private convertOutputData(value: number): number { if (this.widgetUnits !== CapacityUnits.percent) { - return convertLiters(this.volume * (value / 100), this.settings.volumeUnits, ConversionType.to); + return convertLiters(this.volume * (value / 100), this.volumeUnits as CapacityUnits, ConversionType.to); } return value; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts index 954e228e79..632cfe570d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts @@ -56,6 +56,8 @@ export interface LevelCardWidgetSettings extends WidgetConfig { volumeSource: LiquidWidgetDataSourceType; volumeConstant: number; volumeAttributeName: string; + volumeUnitsSource: LiquidWidgetDataSourceType; + volumeUnitsAttributeName: string; volumeUnits: CapacityUnits; volumeFont: Font; volumeColor: string; @@ -257,8 +259,10 @@ export const levelCardDefaultSettings: LevelCardWidgetSettings = { iconColor: '#5469FF', volumeSource: LiquidWidgetDataSourceType.static, volumeConstant: 500, - volumeUnits: CapacityUnits.liters, volumeAttributeName: 'volume', + volumeUnitsSource: LiquidWidgetDataSourceType.static, + volumeUnitsAttributeName: 'volumeUnits', + volumeUnits: CapacityUnits.liters, volumeFont: { family: 'Roboto', size: 14, @@ -375,9 +379,7 @@ export const convertLiters = (value: number, units: CapacityUnits, conversionTyp return conversionType === ConversionType.to ? value / factor : value * factor; }; -export const extractValue = (attributes: Array, attributeName: string): T | undefined => { - return attributes.find(attr => attr.key === attributeName)?.value; -}; +export const extractValue = (attributes: Array, attributeName: string): T | undefined => attributes.find(attr => attr.key === attributeName)?.value; export const valueContainerStyleDefaults = cssTextFromInlineStyle({ width: '100%', @@ -494,6 +496,7 @@ export const updatedFormSettingsValidators = (formGroup: FormGroup) => { const datasourceUnits: string = formGroup.get('datasourceUnits').value; const layout: LevelCardLayout = formGroup.get('layout').value; const volumeSource: LiquidWidgetDataSourceType = formGroup.get('volumeSource').value; + const volumeUnitsSource: LiquidWidgetDataSourceType = formGroup.get('volumeUnitsSource').value; const widgetUnitsSource: LiquidWidgetDataSourceType = formGroup.get('widgetUnitsSource').value; const showTooltipLevel: boolean = formGroup.get('showTooltipLevel').value; const showTooltipDate: boolean = formGroup.get('showTooltipDate').value; @@ -517,7 +520,7 @@ export const updatedFormSettingsValidators = (formGroup: FormGroup) => { if (datasourceUnits !== CapacityUnits.percent) { formGroup.get('volumeSource').enable({emitEvent: false}); - formGroup.get('volumeUnits').enable({emitEvent: false}); + formGroup.get('volumeUnitsSource').enable({emitEvent: false}); if (volumeSource === LiquidWidgetDataSourceType.static) { formGroup.get('volumeConstant').enable({emitEvent: false}); formGroup.get('volumeAttributeName').disable({emitEvent: false}); @@ -525,11 +528,20 @@ export const updatedFormSettingsValidators = (formGroup: FormGroup) => { formGroup.get('volumeConstant').disable({emitEvent: false}); formGroup.get('volumeAttributeName').enable({emitEvent: false}); } + if (volumeUnitsSource === LiquidWidgetDataSourceType.static) { + formGroup.get('volumeUnits').enable({emitEvent: false}); + formGroup.get('volumeUnitsAttributeName').disable({emitEvent: false}); + } else { + formGroup.get('volumeUnits').disable({emitEvent: false}); + formGroup.get('volumeUnitsAttributeName').enable({emitEvent: false}); + } } else { formGroup.get('volumeSource').disable({emitEvent: false}); formGroup.get('volumeConstant').disable({emitEvent: false}); formGroup.get('volumeAttributeName').disable({emitEvent: false}); + formGroup.get('volumeUnitsSource').disable({emitEvent: false}); formGroup.get('volumeUnits').disable({emitEvent: false}); + formGroup.get('volumeUnitsAttributeName').disable({emitEvent: false}); } if (layout === LevelCardLayout.simple) { @@ -557,7 +569,7 @@ export const updatedFormSettingsValidators = (formGroup: FormGroup) => { } formGroup.get('volumeSource').enable({emitEvent: false}); - formGroup.get('volumeUnits').enable({emitEvent: false}); + formGroup.get('volumeUnitsSource').enable({emitEvent: false}); if (volumeSource === LiquidWidgetDataSourceType.static) { formGroup.get('volumeConstant').enable({emitEvent: false}); formGroup.get('volumeAttributeName').disable({emitEvent: false}); @@ -565,6 +577,13 @@ export const updatedFormSettingsValidators = (formGroup: FormGroup) => { formGroup.get('volumeConstant').disable({emitEvent: false}); formGroup.get('volumeAttributeName').enable({emitEvent: false}); } + if (volumeUnitsSource === LiquidWidgetDataSourceType.static) { + formGroup.get('volumeUnits').enable({emitEvent: false}); + formGroup.get('volumeUnitsAttributeName').disable({emitEvent: false}); + } else { + formGroup.get('volumeUnits').disable({emitEvent: false}); + formGroup.get('volumeUnitsAttributeName').enable({emitEvent: false}); + } if (formGroup.get('decimals')) { formGroup.get('decimals').enable({emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html index e742475e80..d549198273 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html @@ -141,10 +141,30 @@ formControlName="volumeAttributeName"> - +
+
+
widgets.liquid-level-card.total-volume-units
+
+ + + + {{ DataSourceTypeTranslations.get(type) | translate }} + + + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts index 5ed3bea446..e25e02d1b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts @@ -158,7 +158,9 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon volumeSource: [settings.volumeSource, []], volumeConstant: [settings.volumeConstant, [Validators.required, Validators.min(0.1)]], volumeAttributeName: [settings.volumeAttributeName, [Validators.required]], + volumeUnitsSource: [settings.volumeUnitsSource, []], volumeUnits: [settings.volumeUnits, [Validators.required]], + volumeUnitsAttributeName: [settings.volumeUnitsAttributeName, [Validators.required]], volumeFont: [settings.volumeFont, []], volumeColor: [settings.volumeColor, []], valueFont: [settings.valueFont, []], @@ -195,7 +197,7 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon protected validatorTriggers(): string[] { return [ 'showBackgroundOverlay', 'showTooltip', 'showTooltipLevel', 'tankSelectionType', 'datasourceUnits', - 'showTooltipDate', 'layout', 'volumeSource', 'widgetUnitsSource' + 'showTooltipDate', 'layout', 'volumeSource', 'widgetUnitsSource', 'volumeUnitsSource' ]; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9140f0071a..6ab58e8e50 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6241,6 +6241,7 @@ "layout": "Layout", "background-overlay": "Value background overlay", "total-volume": "Total volume", + "total-volume-units": "Total volume units", "tank": "Tank", "shape": "Shape", "datasource-units": "Source units", From a1876977652a99b13570403364d5e70115cf454d Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 13:29:08 +0200 Subject: [PATCH 035/137] Refactor device state node test. --- .../engine/action/TbDeviceStateNodeTest.java | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 246e5b3459..2882493f84 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -88,15 +88,11 @@ public class TbDeviceStateNodeTest { @BeforeEach public void setUp() { node = new TbDeviceStateNode(); - config = new TbDeviceStateNodeConfiguration(); + config = new TbDeviceStateNodeConfiguration().defaultConfiguration(); } @Test public void givenDefaultConfiguration_whenInvoked_thenCorrectValuesAreSet() { - // GIVEN-WHEN - config = config.defaultConfiguration(); - - // THEN assertThat(config.getEvent()).isEqualTo(TbMsgType.ACTIVITY_EVENT); assertThat(TbDeviceStateNode.SUPPORTED_EVENTS).isEqualTo(Set.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, @@ -135,15 +131,15 @@ public class TbDeviceStateNodeTest { public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { // GIVEN var asset = new AssetId(UUID.randomUUID()); - var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, asset, new TbMsgMetaData(), "{}"); + var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, asset, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); given(ctxMock.isLocalEntity(asset)).willReturn(true); // WHEN node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should(times(1)).isLocalEntity(asset); - then(ctxMock).should(times(1)).tellSuccess(msg); + then(ctxMock).should().isLocalEntity(asset); + then(ctxMock).should().tellSuccess(msg); then(ctxMock).shouldHaveNoMoreInteractions(); } @@ -158,18 +154,13 @@ public class TbDeviceStateNodeTest { node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should(times(1)).isLocalEntity(DEVICE_ID); - then(ctxMock).should().getTenantId(); - then(ctxMock).should().getSelfId(); - then(ctxMock).should(times(1)).ack(msg); + then(ctxMock).should().ack(msg); then(ctxMock).shouldHaveNoMoreInteractions(); } @ParameterizedTest @MethodSource("provideSupportedEventsAndExpectedMessages") - public void givenSupportedEvent_whenOnMsg_thenCorrectMsgIsSent( - TbMsgType event, TransportProtos.ToCoreMsg expectedToCoreMsg - ) { + public void givenSupportedEvent_whenOnMsg_thenCorrectMsgIsSent(TbMsgType event, TransportProtos.ToCoreMsg expectedToCoreMsg) { // GIVEN config.setEvent(event); var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -188,17 +179,16 @@ public class TbDeviceStateNodeTest { // THEN var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); var callbackCaptor = ArgumentCaptor.forClass(TbQueueCallback.class); - then(tbClusterServiceMock).should(times(1)) - .pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), protoCaptor.capture(), callbackCaptor.capture()); + then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), protoCaptor.capture(), callbackCaptor.capture()); TbQueueCallback actualCallback = callbackCaptor.getValue(); actualCallback.onSuccess(null); - then(ctxMock).should(times(1)).tellSuccess(msg); + then(ctxMock).should().tellSuccess(msg); var throwable = new Throwable(); actualCallback.onFailure(throwable); - then(ctxMock).should(times(1)).tellFailure(msg, throwable); + then(ctxMock).should().tellFailure(msg, throwable); assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); From 653ea4a80097c4ea3b7bce05f96d09f873003091 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 15:07:42 +0200 Subject: [PATCH 036/137] Add check for negative and outdated disconnect event timestamp. --- .../state/DefaultDeviceStateService.java | 16 +++- .../state/DefaultDeviceStateServiceTest.java | 90 ++++++++++++++++++- 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index ee7b4a7e72..941ffef8fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -271,8 +271,19 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService givenOutdatedLastInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent() { + private static Stream provideOutdatedTimestamps() { return Stream.of( Arguments.of(0, 0), Arguments.of(0, 100), From d4d019bedac3d9a0ffe7f98c2423c80814066b7c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 24 Jan 2024 15:21:43 +0200 Subject: [PATCH 037/137] Renaming notifications for edge. Minor refactoring --- .../service/edge/rpc/EdgeGrpcService.java | 4 +- .../service/edge/rpc/EdgeGrpcSession.java | 52 ++++++++++--------- ...CommunicationFailureTriggerProcessor.java} | 26 ++++++---- ...va => EdgeConnectionTriggerProcessor.java} | 18 +++---- .../impl/NotificationRuleExportService.java | 12 ++--- .../impl/NotificationRuleImportService.java | 12 ++--- .../data/notification/NotificationType.java | 4 +- ...CommunicationFailureNotificationInfo.java} | 7 ++- ...va => EdgeConnectionNotificationInfo.java} | 2 +- ...a => EdgeCommunicationFailureTrigger.java} | 11 ++-- ...rigger.java => EdgeConnectionTrigger.java} | 7 ++- ...FailureNotificationRuleTriggerConfig.java} | 4 +- ...nectionNotificationRuleTriggerConfig.java} | 5 +- .../config/NotificationRuleTriggerConfig.java | 4 +- .../config/NotificationRuleTriggerType.java | 4 +- .../common/data/util/TemplateUtils.java | 3 +- .../DefaultNotificationSettingsService.java | 14 ++--- .../notification/DefaultNotifications.java | 39 +++++++------- .../rule-notification-dialog.component.html | 18 +++---- .../rule-notification-dialog.component.ts | 18 +++---- ui-ngx/src/app/shared/models/edge.models.ts | 8 +-- .../app/shared/models/notification.models.ts | 24 ++++----- ...ilure.md => edge_communication_failure.md} | 10 ++-- ...dge_connectivity.md => edge_connection.md} | 17 +----- .../assets/locale/locale.constant-en_US.json | 16 +++--- 25 files changed, 168 insertions(+), 171 deletions(-) rename application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/{EdgeFailureTriggerProcessor.java => EdgeCommunicationFailureTriggerProcessor.java} (65%) rename application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/{EdgeConnectivityTriggerProcessor.java => EdgeConnectionTriggerProcessor.java} (77%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/info/{EdgeFailureNotificationInfo.java => EdgeCommunicationFailureNotificationInfo.java} (89%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/info/{EdgeConnectivityNotificationInfo.java => EdgeConnectionNotificationInfo.java} (95%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/{EdgeFailureTrigger.java => EdgeCommunicationFailureTrigger.java} (84%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/{EdgeConnectivityTrigger.java => EdgeConnectionTrigger.java} (89%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/{EdgeFailureNotificationRuleTriggerConfig.java => EdgeCommunicationFailureNotificationRuleTriggerConfig.java} (85%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/{EdgeConnectivityNotificationRuleTriggerConfig.java => EdgeConnectionNotificationRuleTriggerConfig.java} (84%) rename ui-ngx/src/assets/help/en_US/notification/{edge_failure.md => edge_communication_failure.md} (78%) rename ui-ngx/src/assets/help/en_US/notification/{edge_connectivity.md => edge_connection.md} (84%) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 09092d0621..c23fd98d8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectivityTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectionTrigger; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -467,7 +467,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i edgeState.put(DefaultDeviceStateService.ACTIVITY_STATE, false); edgeState.put(DefaultDeviceStateService.LAST_DISCONNECT_TIME, ts); } - ctx.getNotificationRuleProcessor().process(EdgeConnectivityTrigger.builder() + ctx.getNotificationRuleProcessor().process(EdgeConnectionTrigger.builder() .tenantId(tenantId) .customerId(edge.getCustomerId()) .edgeId(edgeId) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index a42a80c318..44bdfcdb1c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.notification.rule.trigger.EdgeFailureTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeCommunicationFailureTrigger; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -466,22 +466,24 @@ public final class EdgeGrpcSession implements Closeable { if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); if (attempt > 1) { - String errorMsg = String.format("Failed to deliver the batch: {%s}", copy); + String error = "Failed to deliver the batch"; + String failureMsg = String.format("{%s}: {%s}", error, copy); if (attempt == 2) { // Send a failure notification only on the second attempt. // This ensures that failure alerts are sent just once to avoid redundant notifications. - ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId) - .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) + .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); } - log.warn("[{}][{}] {}, attempt: {}", this.tenantId, this.sessionId, errorMsg, attempt); + log.warn("[{}][{}] {}, attempt: {}", this.tenantId, this.sessionId, failureMsg, attempt); } log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { if (this.clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > this.clientMaxInboundMessageSize) { - log.error("[{}][{}][{}] Downlink msg size [{}] exceeds client max inbound message size [{}]. Skipping this message. " + - "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it." + - "Message {}", this.tenantId, edge.getId(), this.sessionId, downlinkMsg.getSerializedSize(), - this.clientMaxInboundMessageSize, downlinkMsg); + String message = String.format("Downlink msg size [{%s}] exceeds client max inbound message size [{%s}]. " + + "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), this.clientMaxInboundMessageSize); + log.error("[{}][{}][{}] {} Message {}", this.tenantId, edge.getId(), this.sessionId, message, downlinkMsg); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) + .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(message).build()); sessionState.getPendingMsgsMap().remove(downlinkMsg.getDownlinkMsgId()); } else { sendDownlinkMsg(ResponseMsg.newBuilder() @@ -492,11 +494,12 @@ public final class EdgeGrpcSession implements Closeable { if (attempt < MAX_DOWNLINK_ATTEMPTS) { scheduleDownlinkMsgsPackSend(attempt + 1); } else { - String errorMsg = String.format("Failed to deliver messages: %s", copy); + String failureMsg = String.format("Failed to deliver messages: %s", copy); log.warn("[{}][{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", this.tenantId, this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy); - ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg) + .error("Failed to deliver messages after " + MAX_DOWNLINK_ATTEMPTS + " attempts").build()); stopCurrentSendDownlinkMsgsTask(false); } } else { @@ -790,10 +793,10 @@ public final class EdgeGrpcSession implements Closeable { } } } catch (Exception e) { - String errorMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); + String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg); log.error("[{}][{}] Can't process uplink msg [{}]", this.tenantId, this.sessionId, uplinkMsg, e); - ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); return Futures.immediateFailedFuture(e); } return Futures.allAsList(result); @@ -817,21 +820,22 @@ public final class EdgeGrpcSession implements Closeable { .setMaxInboundMessageSize(maxInboundMessageSize) .build(); } - String errorMsg = String.format("Failed to validate the edge! Provided request secret: %s", request.getEdgeSecret()); - ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); + String error = "Failed to validate the edge!"; + String failureMsg = String.format("{%s} Provided request secret: %s", error, request.getEdgeSecret()); + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) - .setErrorMsg(errorMsg) + .setErrorMsg(failureMsg) .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } catch (Exception e) { - String errorMsg = "Failed to process edge connection!"; - ctx.getNotificationRuleProcessor().process(EdgeFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) - .customerId(edge.getCustomerId()).edgeName(edge.getName()).errorMsg(errorMsg).build()); - log.error(errorMsg, e); + String failureMsg = "Failed to process edge connection!"; + ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) + .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build()); + log.error(failureMsg, e); return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.SERVER_UNAVAILABLE) - .setErrorMsg(errorMsg) + .setErrorMsg(failureMsg) .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java similarity index 65% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java index afdfa6eb1f..dc54024eec 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeFailureTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java @@ -18,18 +18,18 @@ package org.thingsboard.server.service.notification.rule.trigger; import lombok.RequiredArgsConstructor; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.notification.info.EdgeFailureNotificationInfo; +import org.thingsboard.server.common.data.notification.info.EdgeCommunicationFailureNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; -import org.thingsboard.server.common.data.notification.rule.trigger.EdgeFailureTrigger; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeCommunicationFailureTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; @Service @RequiredArgsConstructor -public class EdgeFailureTriggerProcessor implements NotificationRuleTriggerProcessor { +public class EdgeCommunicationFailureTriggerProcessor implements NotificationRuleTriggerProcessor { @Override - public boolean matchesFilter(EdgeFailureTrigger trigger, EdgeFailureNotificationRuleTriggerConfig triggerConfig) { + public boolean matchesFilter(EdgeCommunicationFailureTrigger trigger, EdgeCommunicationFailureNotificationRuleTriggerConfig triggerConfig) { if (CollectionUtils.isNotEmpty(triggerConfig.getEdges())) { return !triggerConfig.getEdges().contains(trigger.getEdgeId().getId()); } @@ -37,18 +37,26 @@ public class EdgeFailureTriggerProcessor implements NotificationRuleTriggerProce } @Override - public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeFailureTrigger trigger) { - return EdgeFailureNotificationInfo.builder() + public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeCommunicationFailureTrigger trigger) { + return EdgeCommunicationFailureNotificationInfo.builder() .tenantId(trigger.getTenantId()) .edgeId(trigger.getEdgeId()) .customerId(trigger.getCustomerId()) .edgeName(trigger.getEdgeName()) - .errorMsg(trigger.getErrorMsg()) + .failureMsg(truncateFailureMsg(trigger.getFailureMsg())) .build(); } @Override public NotificationRuleTriggerType getTriggerType() { - return NotificationRuleTriggerType.EDGE_FAILURE; + return NotificationRuleTriggerType.EDGE_COMMUNICATION_FAILURE; + } + + private String truncateFailureMsg(String input) { + int maxLength = 800; + if (input != null && input.length() > maxLength) { + return input.substring(0, maxLength); + } + return input; } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java similarity index 77% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java index b5418f6abb..ed1ee74622 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectivityTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeConnectionTriggerProcessor.java @@ -18,19 +18,19 @@ package org.thingsboard.server.service.notification.rule.trigger; import lombok.RequiredArgsConstructor; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.notification.info.EdgeConnectivityNotificationInfo; +import org.thingsboard.server.common.data.notification.info.EdgeConnectionNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; -import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectivityTrigger; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig.EdgeConnectivityEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.EdgeConnectionTrigger; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig.EdgeConnectivityEvent; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; @Service @RequiredArgsConstructor -public class EdgeConnectivityTriggerProcessor implements NotificationRuleTriggerProcessor { +public class EdgeConnectionTriggerProcessor implements NotificationRuleTriggerProcessor { @Override - public boolean matchesFilter(EdgeConnectivityTrigger trigger, EdgeConnectivityNotificationRuleTriggerConfig triggerConfig) { + public boolean matchesFilter(EdgeConnectionTrigger trigger, EdgeConnectionNotificationRuleTriggerConfig triggerConfig) { EdgeConnectivityEvent event = trigger.isConnected() ? EdgeConnectivityEvent.CONNECTED : EdgeConnectivityEvent.DISCONNECTED; if (CollectionUtils.isEmpty(triggerConfig.getNotifyOn()) || !triggerConfig.getNotifyOn().contains(event)) { return false; @@ -42,8 +42,8 @@ public class EdgeConnectivityTriggerProcessor implements NotificationRuleTrigger } @Override - public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeConnectivityTrigger trigger) { - return EdgeConnectivityNotificationInfo.builder() + public RuleOriginatedNotificationInfo constructNotificationInfo(EdgeConnectionTrigger trigger) { + return EdgeConnectionNotificationInfo.builder() .eventType(trigger.isConnected() ? "connected" : "disconnected") .tenantId(trigger.getTenantId()) .customerId(trigger.getCustomerId()) @@ -54,7 +54,7 @@ public class EdgeConnectivityTriggerProcessor implements NotificationRuleTrigger @Override public NotificationRuleTriggerType getTriggerType() { - return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + return NotificationRuleTriggerType.EDGE_CONNECTION; } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java index 61897aaa55..40d7d7c2b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.sync.ie.EntityExportData; @@ -75,13 +75,13 @@ public class NotificationRuleExportService getTemplateData() { @@ -47,7 +46,7 @@ public class EdgeFailureNotificationInfo implements RuleOriginatedNotificationIn "tenantId", tenantId.toString(), "edgeId", edgeId.toString(), "edgeName", edgeName, - "errorMsg", errorMsg + "failureMsg", failureMsg ); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java similarity index 95% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java index 3dfe438ada..e7b6494fdb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectivityNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java @@ -32,7 +32,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf; @NoArgsConstructor @AllArgsConstructor @Builder -public class EdgeConnectivityNotificationInfo implements RuleOriginatedNotificationInfo { +public class EdgeConnectionNotificationInfo implements RuleOriginatedNotificationInfo { private String eventType; private TenantId tenantId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java index 98e1db0527..6400a5c67a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeFailureTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java @@ -27,13 +27,14 @@ import java.util.concurrent.TimeUnit; @Data @Builder -public class EdgeFailureTrigger implements NotificationRuleTrigger { +public class EdgeCommunicationFailureTrigger implements NotificationRuleTrigger { private final TenantId tenantId; private final CustomerId customerId; private final EdgeId edgeId; private final String edgeName; - private final String errorMsg; + private final String failureMsg; + private final String error; @Override public boolean deduplicate() { @@ -42,17 +43,17 @@ public class EdgeFailureTrigger implements NotificationRuleTrigger { @Override public String getDeduplicationKey() { - return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeName, errorMsg); + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeId.toString(), error); } @Override public long getDefaultDeduplicationDuration() { - return TimeUnit.HOURS.toMillis(2); + return TimeUnit.MINUTES.toMillis(30); } @Override public NotificationRuleTriggerType getType() { - return NotificationRuleTriggerType.EDGE_FAILURE; + return NotificationRuleTriggerType.EDGE_COMMUNICATION_FAILURE; } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java index dbbf1e3c74..fe5f6fa6b1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectivityTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java @@ -23,12 +23,11 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; -import java.util.UUID; import java.util.concurrent.TimeUnit; @Data @Builder -public class EdgeConnectivityTrigger implements NotificationRuleTrigger { +public class EdgeConnectionTrigger implements NotificationRuleTrigger { private final TenantId tenantId; private final CustomerId customerId; @@ -48,12 +47,12 @@ public class EdgeConnectivityTrigger implements NotificationRuleTrigger { @Override public long getDefaultDeduplicationDuration() { - return TimeUnit.HOURS.toMillis(3); + return TimeUnit.MINUTES.toMillis(30); } @Override public NotificationRuleTriggerType getType() { - return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + return NotificationRuleTriggerType.EDGE_CONNECTION; } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeCommunicationFailureNotificationRuleTriggerConfig.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeCommunicationFailureNotificationRuleTriggerConfig.java index 7dda80b652..595b0fa7d5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeFailureNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeCommunicationFailureNotificationRuleTriggerConfig.java @@ -27,13 +27,13 @@ import java.util.UUID; @NoArgsConstructor @AllArgsConstructor @Builder -public class EdgeFailureNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { +public class EdgeCommunicationFailureNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { private Set edges; // if empty - all edges @Override public NotificationRuleTriggerType getTriggerType() { - return NotificationRuleTriggerType.EDGE_FAILURE; + return NotificationRuleTriggerType.EDGE_COMMUNICATION_FAILURE; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectionNotificationRuleTriggerConfig.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectionNotificationRuleTriggerConfig.java index e79f9b0968..8c0905cc59 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectivityNotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/EdgeConnectionNotificationRuleTriggerConfig.java @@ -20,7 +20,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotEmpty; import java.util.Set; import java.util.UUID; @@ -28,14 +27,14 @@ import java.util.UUID; @NoArgsConstructor @AllArgsConstructor @Builder -public class EdgeConnectivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { +public class EdgeConnectionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { private Set edges; // if empty - all edges private Set notifyOn; @Override public NotificationRuleTriggerType getTriggerType() { - return NotificationRuleTriggerType.EDGE_CONNECTIVITY; + return NotificationRuleTriggerType.EDGE_CONNECTION; } public enum EdgeConnectivityEvent { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java index ae2130a3f6..15a5e59255 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerConfig.java @@ -36,8 +36,8 @@ import java.io.Serializable; @Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT"), @Type(value = ApiUsageLimitNotificationRuleTriggerConfig.class, name = "API_USAGE_LIMIT"), @Type(value = RateLimitsNotificationRuleTriggerConfig.class, name = "RATE_LIMITS"), - @Type(value = EdgeConnectivityNotificationRuleTriggerConfig.class, name = "EDGE_CONNECTIVITY"), - @Type(value = EdgeFailureNotificationRuleTriggerConfig.class, name = "EDGE_FAILURE"), + @Type(value = EdgeConnectionNotificationRuleTriggerConfig.class, name = "EDGE_CONNECTION"), + @Type(value = EdgeCommunicationFailureNotificationRuleTriggerConfig.class, name = "EDGE_COMMUNICATION_FAILURE"), }) public interface NotificationRuleTriggerConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java index 93d26140f0..8469fac752 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/config/NotificationRuleTriggerType.java @@ -26,8 +26,8 @@ public enum NotificationRuleTriggerType { ALARM_ASSIGNMENT, DEVICE_ACTIVITY, RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, - EDGE_CONNECTIVITY, - EDGE_FAILURE, + EDGE_CONNECTION, + EDGE_COMMUNICATION_FAILURE, NEW_PLATFORM_VERSION(false), ENTITIES_LIMIT(false), API_USAGE_LIMIT(false), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java index 3bc2aac1f8..a6b1c11d42 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.Map; import java.util.function.UnaryOperator; +import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.google.common.base.Strings.nullToEmpty; @@ -49,7 +50,7 @@ public class TemplateUtils { value = FUNCTIONS.get(function).apply(value); } } - return value; + return Matcher.quoteReplacement(value); }); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index 3e02f42cba..b42fdd7645 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -189,8 +189,8 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.alarmComment, tenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.alarmAssignment, affectedUser.getId()); defaultNotifications.create(tenantId, DefaultNotifications.ruleEngineComponentLifecycleFailure, tenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.edgeConnectivity, tenantAdmins.getId()); - defaultNotifications.create(tenantId, DefaultNotifications.edgeFailure, tenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.edgeConnection, tenantAdmins.getId()); + defaultNotifications.create(tenantId, DefaultNotifications.edgeCommunicationFailures, tenantAdmins.getId()); } @Override @@ -210,7 +210,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); } else { - List requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTIVITY, NotificationType.EDGE_FAILURE); + List requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); List existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( tenantId, requiredNotificationTypes, new PageLink(1)) .getData() @@ -227,11 +227,11 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS for (NotificationType type : requiredNotificationTypes) { if (!existingNotificationTypes.contains(type)) { switch (type) { - case EDGE_CONNECTIVITY: - defaultNotifications.create(tenantId, DefaultNotifications.edgeConnectivity, tenantAdmins.getId()); + case EDGE_CONNECTION: + defaultNotifications.create(tenantId, DefaultNotifications.edgeConnection, tenantAdmins.getId()); break; - case EDGE_FAILURE: - defaultNotifications.create(tenantId, DefaultNotifications.edgeFailure, tenantAdmins.getId()); + case EDGE_COMMUNICATION_FAILURE: + defaultNotifications.create(tenantId, DefaultNotifications.edgeCommunicationFailures, tenantAdmins.getId()); break; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 4e2cc5bbad..1ef0006e87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -40,9 +40,9 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm import org.thingsboard.server.common.data.notification.rule.trigger.config.ApiUsageLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectivityNotificationRuleTriggerConfig.EdgeConnectivityEvent; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeFailureNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig.EdgeConnectivityEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NewPlatformVersionNotificationRuleTriggerConfig; @@ -328,34 +328,33 @@ public class DefaultNotifications { .description("Send notification to tenant admins when any Rule chain or Rule node failed to start, update or stop") .build()) .build(); - public static final DefaultNotification edgeConnectivity = DefaultNotification.builder() - .name("Edge connectivity notification") - .type(NotificationType.EDGE_CONNECTIVITY) - .subject("Edge '${edgeName}' is ${eventType}") + public static final DefaultNotification edgeConnection = DefaultNotification.builder() + .name("Edge connection notification") + .type(NotificationType.EDGE_CONNECTION) + .subject("Edge connection status change") .text("Edge '${edgeName}' is now ${eventType}") .icon("info").color(null) .button("Go to Edge").link("/edgeManagement/instances/${edgeId}") .rule(DefaultRule.builder() - .name("Edge connectivity") - .enabled(false) - .triggerConfig(EdgeConnectivityNotificationRuleTriggerConfig.builder() + .name("Edge connection status change") + .triggerConfig(EdgeConnectionNotificationRuleTriggerConfig.builder() .edges(null) .notifyOn(Set.of(EdgeConnectivityEvent.CONNECTED, EdgeConnectivityEvent.DISCONNECTED)) .build()) - .description("Send notification to tenant admins when Edge changes its connectivity state") + .description("Send notification to tenant admins when the connection status between TB and Edge changes") .build()) .build(); - public static final DefaultNotification edgeFailure = DefaultNotification.builder() - .name("Edge error notification") - .type(NotificationType.EDGE_FAILURE) - .subject("Edge '${edgeName}' received error") - .text("Error message: '${errorMsg}'") - .icon("error").color(null) + public static final DefaultNotification edgeCommunicationFailures = DefaultNotification.builder() + .name("Edge communication failure notification") + .type(NotificationType.EDGE_COMMUNICATION_FAILURE) + .subject("Edge '${edgeName}' communication failure occured") + .text("Failure message: '${failureMsg}'") + .icon("error").color(RED_COLOR) .button("Go to Edge").link("/edgeManagement/instances/${edgeId}") .rule(DefaultRule.builder() - .name("Edge error") - .triggerConfig(EdgeFailureNotificationRuleTriggerConfig.builder().edges(null).build()) - .description("Send notification to tenant admins or to assigned customers to Edge when error occurs") + .name("Edge communication failure") + .triggerConfig(EdgeCommunicationFailureNotificationRuleTriggerConfig.builder().edges(null).build()) + .description("Send notification to tenant admins when communication failures occur") .build()) .build(); diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html index 852cb7a549..f4ae83143f 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.html @@ -408,10 +408,10 @@ - + {{ 'notification.edge-trigger-settings' | translate }} -
+
notification.filter @@ -426,9 +426,9 @@ notification.notify-on - - {{ edgeConnectivityEventTranslationMap.get(edgeEvent) | translate }} + placeholder="{{ !edgeConnectionTemplateForm.get('triggerConfig.notifyOn').value?.length ? ('event.all-events' | translate) : '' }}"> + + {{ edgeConnectionEventTranslationMap.get(edgeEvent) | translate }} @@ -445,10 +445,10 @@ - + {{ 'notification.edge-trigger-settings' | translate }} -
+
notification.filter diff --git a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts index 099897af7c..34ff605027 100644 --- a/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification/rule/rule-notification-dialog.component.ts @@ -68,7 +68,7 @@ import { } from '@shared/models/api-usage.models'; import { LimitedApi, LimitedApiTranslationMap } from '@shared/models/limited-api.models'; import { StringItemsOption } from '@shared/components/string-items-list.component'; -import { EdgeConnectivityEvent, EdgeConnectivityEventTranslationMap } from '@shared/models/edge.models'; +import { EdgeConnectionEvent, EdgeConnectionEventTranslationMap } from '@shared/models/edge.models'; export interface RuleNotificationDialogData { rule?: NotificationRule; @@ -99,8 +99,8 @@ export class RuleNotificationDialogComponent extends apiUsageLimitTemplateForm: FormGroup; newPlatformVersionTemplateForm: FormGroup; rateLimitsTemplateForm: FormGroup; - edgeFailureTemplateForm: FormGroup; - edgeConnectivityTemplateForm: FormGroup; + edgeCommunicationFailureTemplateForm: FormGroup; + edgeConnectionTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[]; @@ -135,8 +135,8 @@ export class RuleNotificationDialogComponent extends apiFeatures: ApiFeature[] = Object.values(ApiFeature); apiFeatureTranslationMap = ApiFeatureTranslationMap; - edgeConnectivityEvents: EdgeConnectivityEvent[] = Object.values(EdgeConnectivityEvent); - edgeConnectivityEventTranslationMap = EdgeConnectivityEventTranslationMap; + edgeConnectionEvents: EdgeConnectionEvent[] = Object.values(EdgeConnectionEvent); + edgeConnectionEventTranslationMap = EdgeConnectionEventTranslationMap; limitedApis: StringItemsOption[]; @@ -227,14 +227,14 @@ export class RuleNotificationDialogComponent extends } }); - this.edgeConnectivityTemplateForm = this.fb.group({ + this.edgeConnectionTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ edges: [null], notifyOn: [null] }) }); - this.edgeFailureTemplateForm = this.fb.group({ + this.edgeCommunicationFailureTemplateForm = this.fb.group({ triggerConfig: this.fb.group({ edges: [null] }) @@ -348,8 +348,8 @@ export class RuleNotificationDialogComponent extends [TriggerType.API_USAGE_LIMIT, this.apiUsageLimitTemplateForm], [TriggerType.NEW_PLATFORM_VERSION, this.newPlatformVersionTemplateForm], [TriggerType.RATE_LIMITS, this.rateLimitsTemplateForm], - [TriggerType.EDGE_FAILURE, this.edgeFailureTemplateForm], - [TriggerType.EDGE_CONNECTIVITY, this.edgeConnectivityTemplateForm] + [TriggerType.EDGE_COMMUNICATION_FAILURE, this.edgeCommunicationFailureTemplateForm], + [TriggerType.EDGE_CONNECTION, this.edgeConnectionTemplateForm] ]); if (data.isAdd || data.isCopy) { diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 4da5f089a0..569b2bec8a 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -191,14 +191,14 @@ export enum EdgeInstructionsMethod { export const edgeVersionAttributeKey = 'edgeVersion'; -export enum EdgeConnectivityEvent { +export enum EdgeConnectionEvent { CONNECTED= 'CONNECTED', DISCONNECTED = 'DISCONNECTED' } -export const EdgeConnectivityEventTranslationMap = new Map( +export const EdgeConnectionEventTranslationMap = new Map( [ - [EdgeConnectivityEvent.CONNECTED, 'edge-event.connected'], - [EdgeConnectivityEvent.DISCONNECTED, 'edge-event.disconnected'] + [EdgeConnectionEvent.CONNECTED, 'edge.connected'], + [EdgeConnectionEvent.DISCONNECTED, 'edge.disconnected'] ] ); diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index d23e6c5f84..ae945b243c 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -475,8 +475,8 @@ export enum NotificationType { NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION', RULE_NODE = 'RULE_NODE', RATE_LIMITS = 'RATE_LIMITS', - EDGE_CONNECTIVITY = 'EDGE_CONNECTIVITY', - EDGE_FAILURE = 'EDGE_FAILURE' + EDGE_CONNECTION = 'EDGE_CONNECTION', + EDGE_COMMUNICATION_FAILURE = 'EDGE_COMMUNICATION_FAILURE' } export const NotificationTypeIcons = new Map([ @@ -588,16 +588,16 @@ export const NotificationTemplateTypeTranslateMap = new Map([ @@ -628,8 +628,8 @@ export const TriggerTypeTranslationMap = new Map([ [TriggerType.API_USAGE_LIMIT, 'notification.trigger.api-usage-limit'], [TriggerType.NEW_PLATFORM_VERSION, 'notification.trigger.new-platform-version'], [TriggerType.RATE_LIMITS, 'notification.trigger.rate-limits'], - [TriggerType.EDGE_CONNECTIVITY, 'notification.trigger.edge-connectivity'], - [TriggerType.EDGE_FAILURE, 'notification.trigger.edge-failure'] + [TriggerType.EDGE_CONNECTION, 'notification.trigger.edge-connection'], + [TriggerType.EDGE_COMMUNICATION_FAILURE, 'notification.trigger.edge-communication-failure'] ]); export interface NotificationUserSettings { diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_failure.md b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md similarity index 78% rename from ui-ngx/src/assets/help/en_US/notification/edge_failure.md rename to ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md index d0aaba21a0..03f2a2816a 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_failure.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md @@ -11,7 +11,7 @@ Available template parameters: * `edgeId` - the edge id as uuid string; * `edgeName` - the name of the edge; -* `errorMsg` - the string representation of the error, occurred on the Edge; +* `failureMsg` - the string representation of the failure, occurred on the Edge; Parameter names must be wrapped using `${...}`. For example: `${edgeName}`. You may also modify the value of the parameter with one of the suffixes: @@ -28,14 +28,14 @@ Let's assume the notification about the failing of processing connection to Edge The following template: ```text -Edge '${edgeName}' received error +Edge '${edgeName}' communication failure occurred {:copy-code} ``` will be transformed to: ```text -Edge 'DatacenterEdge' received error +Edge 'DatacenterEdge' communication failure occurred ```
@@ -43,14 +43,14 @@ Edge 'DatacenterEdge' received error The following template: ```text -Error message: '${errorMsg}' +Failure message: '${failureMsg}' {:copy-code} ``` will be transformed to: ```text -Error message: 'Failed to process edge connection!' +Failure message: 'Failed to process edge connection!' ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_connectivity.md b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md similarity index 84% rename from ui-ngx/src/assets/help/en_US/notification/edge_connectivity.md rename to ui-ngx/src/assets/help/en_US/notification/edge_connection.md index 2044ecf708..fed6c0372e 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_connectivity.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md @@ -28,30 +28,17 @@ Let's assume the notification about the connecting Edge into the ThingsBoard. The following template: ```text -Edge '${edgeName}' is ${eventType} +Edge '${edgeName}' is now ${eventType} {:copy-code} ``` will be transformed to: ```text -Edge 'DatacenterEdge' is connected +Edge 'DatacenterEdge' is now connected ```
-The following template: - -```text -"Edge '${edgeName}' is now ${eventType}" -{:copy-code} -``` - -will be transformed to: - -```text -Edge 'DatacenterEdge' is now connected -``` -

diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 63b791a6ef..74879d0420 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2030,7 +2030,9 @@ "missing-related-rule-chains-title": "Edge has missing related rule chain(s)", "missing-related-rule-chains-text": "Assigned to edge rule chain(s) use rule nodes that forward message(s) to rule chain(s) that are not assigned to this edge.

List of missing rule chain(s):
{{missingRuleChains}}", "upgrade-instructions": "Upgrade Instructions", - "widget-datasource-error": "This widget supports only EDGE entity datasource" + "widget-datasource-error": "This widget supports only EDGE entity datasource", + "connected": "Connected", + "disconnected": "Disconnected" }, "edge-event": { "type-dashboard": "Dashboard", @@ -2073,9 +2075,7 @@ "action-type-assigned-to-edge": "Assigned to Edge", "action-type-unassigned-from-edge": "Unassigned from Edge", "action-type-credentials-request": "Credentials Request", - "action-type-entity-merge-request": "Entity Merge Request", - "connected": "Connected", - "disconnected": "Disconnected" + "action-type-entity-merge-request": "Entity Merge Request" }, "error": { "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", @@ -3430,8 +3430,8 @@ "rule-node": "Rule node", "new-platform-version": "New platform version", "rate-limits": "Exceeded rate limits", - "edge-failure": "Edge error", - "edge-connectivity": "Edge connectivity" + "edge-communication-failure": "Edge communication failure", + "edge-connection": "Edge connection" }, "templates": "Templates", "notification-templates": "Notifications / Templates", @@ -3452,8 +3452,8 @@ "rule-engine-lifecycle-event": "Rule engine lifecycle event", "new-platform-version": "New platform version", "rate-limits": "Exceeded rate limits", - "edge-connectivity": "Edge connectivity", - "edge-failure": "Edge failure", + "edge-connection": "Edge connection", + "edge-communication-failure": "Edge communication failure", "trigger": "Trigger", "trigger-required": "Trigger is required" }, From c81d9438540905133ab8568af4bd361411a15990 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 16:11:19 +0200 Subject: [PATCH 038/137] Add check for negative and outdated connect event timestamp. --- .../state/DefaultDeviceStateService.java | 13 ++- .../state/DefaultDeviceStateServiceTest.java | 88 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 941ffef8fa..b400b4f65a 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -226,8 +226,19 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService Date: Wed, 24 Jan 2024 16:25:26 +0200 Subject: [PATCH 039/137] Remove excessive brackets in switch-case --- .../rule/engine/action/TbDeviceStateNode.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 63ebf09b9c..ccc252c912 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -93,25 +93,20 @@ public class TbDeviceStateNode implements TbNode { return; } switch (event) { - case CONNECT_EVENT: { + case CONNECT_EVENT: sendDeviceConnectMsg(ctx, msg); break; - } - case ACTIVITY_EVENT: { + case ACTIVITY_EVENT: sendDeviceActivityMsg(ctx, msg); break; - } - case DISCONNECT_EVENT: { + case DISCONNECT_EVENT: sendDeviceDisconnectMsg(ctx, msg); break; - } - case INACTIVITY_EVENT: { + case INACTIVITY_EVENT: sendDeviceInactivityMsg(ctx, msg); break; - } - default: { + default: ctx.tellFailure(msg, new IllegalStateException("Configured event [" + event + "] is not supported!")); - } } } From f22cf63f108517bd5c6ce045e29281fa3a0355bf Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Wed, 24 Jan 2024 16:27:15 +0200 Subject: [PATCH 040/137] Changed validators for path fields --- .../widget/lib/gateway/gateway-configuration.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts index e43f22ff71..1c65920179 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts @@ -127,11 +127,11 @@ export class GatewayConfigurationComponent implements OnInit { type: [StorageTypes.MEMORY, [Validators.required]], read_records_count: [100, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], max_records_count: [100000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], - data_folder_path: ['./data/', [Validators.pattern(/^[^\s]+$/)]], + data_folder_path: ['./data/', [Validators.required]], max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_records_per_file: [10000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - data_file_path: ['./data/data.db', [Validators.pattern(/^[^\s]+$/)]], + data_file_path: ['./data/data.db', [Validators.required]], messages_ttl_check_in_hours: [1, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], messages_ttl_in_days: [7, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], @@ -240,7 +240,7 @@ export class GatewayConfigurationComponent implements OnInit { storageGroup.get('read_records_count').updateValueAndValidity({emitEvent: false}); storageGroup.get('max_records_count').updateValueAndValidity({emitEvent: false}); } else if (type === StorageTypes.FILE) { - storageGroup.get('data_folder_path').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); + storageGroup.get('data_folder_path').addValidators([Validators.required]); storageGroup.get('max_file_count').addValidators( [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); storageGroup.get('max_read_records_count').addValidators( @@ -252,7 +252,7 @@ export class GatewayConfigurationComponent implements OnInit { storageGroup.get('max_read_records_count').updateValueAndValidity({emitEvent: false}); storageGroup.get('max_records_per_file').updateValueAndValidity({emitEvent: false}); } else if (type === StorageTypes.SQLITE) { - storageGroup.get('data_file_path').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); + storageGroup.get('data_file_path').addValidators([Validators.required]); storageGroup.get('messages_ttl_check_in_hours').addValidators( [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); storageGroup.get('messages_ttl_in_days').addValidators( From 5e846116a885c655bb471776153f843c7f419a34 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 17:27:10 +0200 Subject: [PATCH 041/137] Use consumer instead of runnable in SimpleTbQueueCallback --- .../actors/ruleChain/DefaultTbContext.java | 40 ++++++++++++------- .../queue/common/SimpleTbQueueCallback.java | 6 +-- .../rule/engine/action/TbDeviceStateNode.java | 2 +- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index baae03761b..1e7d5d24e9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -214,13 +214,19 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "To Root Rule Chain"); } - mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, t -> { - if (onFailure != null) { - onFailure.accept(t); - } else { - log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); - } - })); + mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback( + metadata -> { + if (onSuccess != null) { + onSuccess.run(); + } + }, + t -> { + if (onFailure != null) { + onFailure.accept(t); + } else { + log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); + } + })); } @Override @@ -306,13 +312,19 @@ class DefaultTbContext implements TbContext { relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, relationType, null, failureMessage)); } - mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, t -> { - if (onFailure != null) { - onFailure.accept(t); - } else { - log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); - } - })); + mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback( + metadata -> { + if (onSuccess != null) { + onSuccess.run(); + } + }, + t -> { + if (onFailure != null) { + onFailure.accept(t); + } else { + log.debug("[{}] Failed to put item into queue!", nodeCtx.getTenantId().getId(), t); + } + })); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java index 5f86060d48..d153998ab4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/SimpleTbQueueCallback.java @@ -22,10 +22,10 @@ import java.util.function.Consumer; public class SimpleTbQueueCallback implements TbQueueCallback { - private final Runnable onSuccess; + private final Consumer onSuccess; private final Consumer onFailure; - public SimpleTbQueueCallback(Runnable onSuccess, Consumer onFailure) { + public SimpleTbQueueCallback(Consumer onSuccess, Consumer onFailure) { this.onSuccess = onSuccess; this.onFailure = onFailure; } @@ -33,7 +33,7 @@ public class SimpleTbQueueCallback implements TbQueueCallback { @Override public void onSuccess(TbQueueMsgMetadata metadata) { if (onSuccess != null) { - onSuccess.run(); + onSuccess.accept(metadata); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index ccc252c912..65d7e0979e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -183,7 +183,7 @@ public class TbDeviceStateNode implements TbNode { } private TbQueueCallback getMsgEnqueuedCallback(TbContext ctx, TbMsg msg) { - return new SimpleTbQueueCallback(() -> ctx.tellSuccess(msg), t -> ctx.tellFailure(msg, t)); + return new SimpleTbQueueCallback(metadata -> ctx.tellSuccess(msg), t -> ctx.tellFailure(msg, t)); } } From c65a9c2e0d559bc114d4b366b86af79a64d1c85d Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 24 Jan 2024 17:58:24 +0200 Subject: [PATCH 042/137] Remove unnecessary originator locality check --- .../rule/engine/action/TbDeviceStateNode.java | 9 +-------- .../engine/action/TbDeviceStateNodeTest.java | 18 ------------------ 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 65d7e0979e..f28a3e4467 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -81,14 +81,7 @@ public class TbDeviceStateNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - var originator = msg.getOriginator(); - if (!ctx.isLocalEntity(originator)) { - log.warn("[{}][{}] Received message from non-local entity [{}]!", - ctx.getTenantId().getId(), ctx.getSelfId().getId(), originator.getId()); - ctx.ack(msg); - return; - } - if (!EntityType.DEVICE.equals(originator.getEntityType())) { + if (!EntityType.DEVICE.equals(msg.getOriginator().getEntityType())) { ctx.tellSuccess(msg); return; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 2882493f84..988d8911a4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -132,32 +132,15 @@ public class TbDeviceStateNodeTest { // GIVEN var asset = new AssetId(UUID.randomUUID()); var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, asset, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); - given(ctxMock.isLocalEntity(asset)).willReturn(true); // WHEN node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should().isLocalEntity(asset); then(ctxMock).should().tellSuccess(msg); then(ctxMock).shouldHaveNoMoreInteractions(); } - @Test - public void givenNonLocalOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { - // GIVEN - given(ctxMock.isLocalEntity(msg.getOriginator())).willReturn(false); - given(ctxMock.getSelfId()).willReturn(new RuleNodeId(UUID.randomUUID())); - given(ctxMock.getTenantId()).willReturn(TENANT_ID); - - // WHEN - node.onMsg(ctxMock, msg); - - // THEN - then(ctxMock).should().ack(msg); - then(ctxMock).shouldHaveNoMoreInteractions(); - } - @ParameterizedTest @MethodSource("provideSupportedEventsAndExpectedMessages") public void givenSupportedEvent_whenOnMsg_thenCorrectMsgIsSent(TbMsgType event, TransportProtos.ToCoreMsg expectedToCoreMsg) { @@ -171,7 +154,6 @@ public class TbDeviceStateNodeTest { } given(ctxMock.getTenantId()).willReturn(TENANT_ID); given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); - given(ctxMock.isLocalEntity(msg.getOriginator())).willReturn(true); // WHEN node.onMsg(ctxMock, msg); From a571153b7cf0826302030c0e6d50f7d52b8c375f Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 24 Jan 2024 18:08:01 +0200 Subject: [PATCH 043/137] Single update message for multiple queues --- .../entitiy/queue/DefaultTbQueueService.java | 115 ++++++++---------- .../queue/DefaultTbClusterService.java | 66 +++++----- .../queue/DefaultTbCoreConsumerService.java | 10 +- .../DefaultTbRuleEngineConsumerService.java | 71 ++++++----- .../service/queue/TbCoreConsumerStats.java | 4 +- .../discovery/HashPartitionServiceTest.java | 6 +- .../queue/DefaultTbClusterServiceTest.java | 9 +- .../server/queue/TbQueueClusterService.java | 8 +- common/proto/src/main/proto/queue.proto | 12 +- .../queue/discovery/HashPartitionService.java | 41 ++++--- .../queue/discovery/PartitionService.java | 4 +- .../service/DefaultTransportService.java | 13 +- .../server/dao/queue/BaseQueueService.java | 3 - 13 files changed, 184 insertions(+), 178 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index b0ef8f4cde..e13342eee0 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -50,22 +50,15 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb public Queue saveQueue(Queue queue) { boolean create = queue.getId() == null; Queue oldQueue; - if (create) { oldQueue = null; } else { oldQueue = queueService.findQueueById(queue.getTenantId(), queue.getId()); } - //TODO: add checkNotNull Queue savedQueue = queueService.saveQueue(queue); - - if (create) { - onQueueCreated(savedQueue); - } else { - onQueueUpdated(savedQueue, oldQueue); - } - + createTopicsIfNeeded(savedQueue, oldQueue); + tbClusterService.onQueuesUpdate(List.of(savedQueue)); return savedQueue; } @@ -73,54 +66,14 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb public void deleteQueue(TenantId tenantId, QueueId queueId) { Queue queue = queueService.findQueueById(tenantId, queueId); queueService.deleteQueue(tenantId, queueId); - onQueueDeleted(queue); + tbClusterService.onQueuesDelete(List.of(queue)); } @Override public void deleteQueueByQueueName(TenantId tenantId, String queueName) { Queue queue = queueService.findQueueByTenantIdAndNameInternal(tenantId, queueName); queueService.deleteQueue(tenantId, queue.getId()); - onQueueDeleted(queue); - } - - private void onQueueCreated(Queue queue) { - for (int i = 0; i < queue.getPartitions(); i++) { - tbQueueAdmin.createTopicIfNotExists( - new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(), - queue.getCustomProperties() - ); - } - - tbClusterService.onQueueChange(queue); - } - - private void onQueueUpdated(Queue queue, Queue oldQueue) { - int oldPartitions = oldQueue.getPartitions(); - int currentPartitions = queue.getPartitions(); - - if (currentPartitions != oldPartitions) { - if (currentPartitions > oldPartitions) { - log.info("Added [{}] new partitions to [{}] queue", currentPartitions - oldPartitions, queue.getName()); - for (int i = oldPartitions; i < currentPartitions; i++) { - tbQueueAdmin.createTopicIfNotExists( - new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(), - queue.getCustomProperties() - ); - } - tbClusterService.onQueueChange(queue); - } else { - log.info("Removed [{}] partitions from [{}] queue", oldPartitions - currentPartitions, queue.getName()); - tbClusterService.onQueueChange(queue); - // TODO: move all the messages left in old partitions and delete topics - } - } else if (!oldQueue.equals(queue)) { - tbClusterService.onQueueChange(queue); - } - } - - private void onQueueDeleted(Queue queue) { - tbClusterService.onQueueDelete(queue); -// queueStatsService.deleteQueueStatsByQueueId(tenantId, queueId); + tbClusterService.onQueuesDelete(List.of(queue)); } @Override @@ -176,26 +129,56 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb log.debug("[{}] Handling profile queue config update: creating queues {}, updating {}, deleting {}. Affected tenants: {}", newTenantProfile.getUuidId(), toCreate, toUpdate, toRemove, tenantIds); } - tenantIds.forEach(tenantId -> { - toCreate.forEach(key -> saveQueue(new Queue(tenantId, newQueues.get(key)))); - toUpdate.forEach(key -> { - Queue queueToUpdate = new Queue(tenantId, newQueues.get(key)); - Queue foundQueue = queueService.findQueueByTenantIdAndName(tenantId, key); - queueToUpdate.setId(foundQueue.getId()); - queueToUpdate.setCreatedTime(foundQueue.getCreatedTime()); + List updated = new ArrayList<>(); + List deleted = new ArrayList<>(); + for (TenantId tenantId : tenantIds) { + for (String name : toCreate) { + updated.add(new Queue(tenantId, newQueues.get(name))); + } - if (!queueToUpdate.equals(foundQueue)) { - saveQueue(queueToUpdate); + for (String name : toUpdate) { + Queue queue = new Queue(tenantId, newQueues.get(name)); + Queue foundQueue = queueService.findQueueByTenantIdAndName(tenantId, name); + if (foundQueue != null) { + queue.setId(foundQueue.getId()); + queue.setCreatedTime(foundQueue.getCreatedTime()); } - }); + if (!queue.equals(foundQueue)) { + updated.add(queue); + createTopicsIfNeeded(queue, foundQueue); + } + } + + for (String name : toRemove) { + Queue queue = queueService.findQueueByTenantIdAndNameInternal(tenantId, name); + deleted.add(queue); + } + } - toRemove.forEach(q -> { - Queue queue = queueService.findQueueByTenantIdAndNameInternal(tenantId, q); - QueueId queueIdForRemove = queue.getId(); - deleteQueue(tenantId, queueIdForRemove); + if (!updated.isEmpty()) { + updated = updated.stream() + .map(queueService::saveQueue) + .collect(Collectors.toList()); + tbClusterService.onQueuesUpdate(updated); + } + if (!deleted.isEmpty()) { + deleted.forEach(queue -> { + queueService.deleteQueue(queue.getTenantId(), queue.getId()); }); - }); + tbClusterService.onQueuesDelete(deleted); + } + } + + private void createTopicsIfNeeded(Queue queue, Queue oldQueue) { + int newPartitions = queue.getPartitions(); + int oldPartitions = oldQueue != null ? oldQueue.getPartitions() : 0; + for (int i = oldPartitions; i < newPartitions; i++) { + tbQueueAdmin.createTopicIfNotExists( + new TopicPartitionInfo(queue.getTopic(), queue.getTenantId(), i, false).getFullTopicName(), + queue.getCustomProperties() + ); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 14930912d1..1f27f06502 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -59,6 +59,8 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; +import org.thingsboard.server.gen.transport.TransportProtos.QueueDeleteMsg; +import org.thingsboard.server.gen.transport.TransportProtos.QueueUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -68,8 +70,8 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; @@ -77,9 +79,11 @@ import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static org.thingsboard.server.common.util.ProtoUtils.toProto; @@ -552,40 +556,40 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void onQueueChange(Queue queue) { - log.trace("[{}][{}] Processing queue change [{}] event", queue.getTenantId(), queue.getId(), queue.getName()); - - TransportProtos.QueueUpdateMsg queueUpdateMsg = TransportProtos.QueueUpdateMsg.newBuilder() - .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) - .setQueueIdMSB(queue.getId().getId().getMostSignificantBits()) - .setQueueIdLSB(queue.getId().getId().getLeastSignificantBits()) - .setQueueName(queue.getName()) - .setQueueTopic(queue.getTopic()) - .setPartitions(queue.getPartitions()) - .build(); - - ToRuleEngineNotificationMsg ruleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().setQueueUpdateMsg(queueUpdateMsg).build(); - ToCoreNotificationMsg coreMsg = ToCoreNotificationMsg.newBuilder().setQueueUpdateMsg(queueUpdateMsg).build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setQueueUpdateMsg(queueUpdateMsg).build(); + public void onQueuesUpdate(List queues) { + List queueUpdateMsgs = queues.stream() + .map(queue -> QueueUpdateMsg.newBuilder() + .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) + .setQueueIdMSB(queue.getId().getId().getMostSignificantBits()) + .setQueueIdLSB(queue.getId().getId().getLeastSignificantBits()) + .setQueueName(queue.getName()) + .setQueueTopic(queue.getTopic()) + .setPartitions(queue.getPartitions()) + .build()) + .collect(Collectors.toList()); + + ToRuleEngineNotificationMsg ruleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().addAllQueueUpdateMsgs(queueUpdateMsgs).build(); + ToCoreNotificationMsg coreMsg = ToCoreNotificationMsg.newBuilder().addAllQueueUpdateMsgs(queueUpdateMsgs).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().addAllQueueUpdateMsgs(queueUpdateMsgs).build(); doSendQueueNotifications(ruleEngineMsg, coreMsg, transportMsg); } @Override - public void onQueueDelete(Queue queue) { - log.trace("[{}][{}] Processing queue delete [{}] event", queue.getTenantId(), queue.getId(), queue.getName()); - - TransportProtos.QueueDeleteMsg queueDeleteMsg = TransportProtos.QueueDeleteMsg.newBuilder() - .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) - .setQueueIdMSB(queue.getId().getId().getMostSignificantBits()) - .setQueueIdLSB(queue.getId().getId().getLeastSignificantBits()) - .setQueueName(queue.getName()) - .build(); - - ToRuleEngineNotificationMsg ruleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().setQueueDeleteMsg(queueDeleteMsg).build(); - ToCoreNotificationMsg coreMsg = ToCoreNotificationMsg.newBuilder().setQueueDeleteMsg(queueDeleteMsg).build(); - ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setQueueDeleteMsg(queueDeleteMsg).build(); + public void onQueuesDelete(List queues) { + List queueDeleteMsgs = queues.stream() + .map(queue -> QueueDeleteMsg.newBuilder() + .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) + .setQueueIdMSB(queue.getId().getId().getMostSignificantBits()) + .setQueueIdLSB(queue.getId().getId().getLeastSignificantBits()) + .setQueueName(queue.getName()) + .build()) + .collect(Collectors.toList()); + + ToRuleEngineNotificationMsg ruleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().addAllQueueDeleteMsgs(queueDeleteMsgs).build(); + ToCoreNotificationMsg coreMsg = ToCoreNotificationMsg.newBuilder().addAllQueueDeleteMsgs(queueDeleteMsgs).build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().addAllQueueDeleteMsgs(queueDeleteMsgs).build(); doSendQueueNotifications(ruleEngineMsg, coreMsg, transportMsg); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 8e5fde9ae8..f544c4fe25 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -391,13 +391,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService 0) { + partitionService.updateQueues(toCoreNotification.getQueueUpdateMsgsList()); callback.onSuccess(); - } else if (toCoreNotification.hasQueueDeleteMsg()) { - TransportProtos.QueueDeleteMsg queue = toCoreNotification.getQueueDeleteMsg(); - partitionService.removeQueue(queue); + } else if (toCoreNotification.getQueueDeleteMsgsCount() > 0) { + partitionService.removeQueues(toCoreNotification.getQueueDeleteMsgsList()); callback.onSuccess(); } else if (toCoreNotification.hasVcResponseMsg()) { vcQueueService.processResponse(toCoreNotification.getVcResponseMsg()); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 8cc477999f..6a205ed5a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -36,6 +36,8 @@ import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.QueueDeleteMsg; +import org.thingsboard.server.gen.transport.TransportProtos.QueueUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; @@ -164,11 +166,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< , proto.getResponse(), error); tbDeviceRpcService.processRpcResponseFromDevice(response); callback.onSuccess(); - } else if (nfMsg.hasQueueUpdateMsg()) { - updateQueue(nfMsg.getQueueUpdateMsg()); + } else if (nfMsg.getQueueUpdateMsgsCount() > 0) { + updateQueues(nfMsg.getQueueUpdateMsgsList()); callback.onSuccess(); - } else if (nfMsg.hasQueueDeleteMsg()) { - deleteQueue(nfMsg.getQueueDeleteMsg()); + } else if (nfMsg.getQueueDeleteMsgsCount() > 0) { + deleteQueues(nfMsg.getQueueDeleteMsgsList()); callback.onSuccess(); } else { log.trace("Received notification with missing handler"); @@ -176,39 +178,48 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< } } - private void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg) { - log.info("Received queue update msg: [{}]", queueUpdateMsg); - TenantId tenantId = new TenantId(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB())); - if (partitionService.isManagedByCurrentService(tenantId)) { - QueueId queueId = new QueueId(new UUID(queueUpdateMsg.getQueueIdMSB(), queueUpdateMsg.getQueueIdLSB())); - String queueName = queueUpdateMsg.getQueueName(); - QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueName, tenantId); - Queue queue = queueService.findQueueById(tenantId, queueId); - - TbRuleEngineQueueConsumerManager consumerManager = getOrCreateConsumer(queueKey); - Queue oldQueue = consumerManager.getQueue(); - consumerManager.update(queue); - - if (oldQueue != null && queue.getPartitions() == oldQueue.getPartitions()) { - return; + private void updateQueues(List queueUpdateMsgs) { + boolean partitionsChanged = false; + for (QueueUpdateMsg queueUpdateMsg : queueUpdateMsgs) { + log.info("Received queue update msg: [{}]", queueUpdateMsg); + TenantId tenantId = new TenantId(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB())); + if (partitionService.isManagedByCurrentService(tenantId)) { + QueueId queueId = new QueueId(new UUID(queueUpdateMsg.getQueueIdMSB(), queueUpdateMsg.getQueueIdLSB())); + String queueName = queueUpdateMsg.getQueueName(); + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueName, tenantId); + Queue queue = queueService.findQueueById(tenantId, queueId); + + TbRuleEngineQueueConsumerManager consumerManager = getOrCreateConsumer(queueKey); + Queue oldQueue = consumerManager.getQueue(); + consumerManager.update(queue); + + if (oldQueue == null || queue.getPartitions() != oldQueue.getPartitions()) { + partitionsChanged = true; + } + } else { + partitionsChanged = true; } } - partitionService.updateQueue(queueUpdateMsg); - partitionService.recalculatePartitions(ctx.getServiceInfoProvider().getServiceInfo(), - new ArrayList<>(partitionService.getOtherServices(ServiceType.TB_RULE_ENGINE))); + if (partitionsChanged) { + partitionService.updateQueues(queueUpdateMsgs); + partitionService.recalculatePartitions(ctx.getServiceInfoProvider().getServiceInfo(), + new ArrayList<>(partitionService.getOtherServices(ServiceType.TB_RULE_ENGINE))); + } } - private void deleteQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg) { - log.info("Received queue delete msg: [{}]", queueDeleteMsg); - TenantId tenantId = new TenantId(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); - QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); - var consumerManager = consumers.remove(queueKey); - if (consumerManager != null) { - consumerManager.delete(true); + private void deleteQueues(List queueDeleteMsgs) { + for (QueueDeleteMsg queueDeleteMsg : queueDeleteMsgs) { + log.info("Received queue delete msg: [{}]", queueDeleteMsg); + TenantId tenantId = new TenantId(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); + var consumerManager = consumers.remove(queueKey); + if (consumerManager != null) { + consumerManager.delete(true); + } } - partitionService.removeQueue(queueDeleteMsg); + partitionService.removeQueues(queueDeleteMsgs); partitionService.recalculatePartitions(ctx.getServiceInfoProvider().getServiceInfo(), new ArrayList<>(partitionService.getOtherServices(ServiceType.TB_RULE_ENGINE))); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java index 5ff72e97e3..da3171bfc0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java @@ -184,9 +184,9 @@ public class TbCoreConsumerStats { toCoreNfEdgeSyncResponseCounter.increment(); } else if (!msg.getFromEdgeSyncResponseMsg().isEmpty()) { toCoreNfEdgeSyncResponseCounter.increment(); - } else if (msg.hasQueueUpdateMsg()) { + } else if (msg.getQueueUpdateMsgsCount() > 0) { toCoreNfQueueUpdateCounter.increment(); - } else if (msg.hasQueueDeleteMsg()) { + } else if (msg.getQueueDeleteMsgsCount() > 0) { toCoreNfQueueDeleteCounter.increment(); } else if (msg.hasVcResponseMsg()) { toCoreNfVersionControlResponseCounter.increment(); diff --git a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java index 4ea648119b..669b3dda4a 100644 --- a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java @@ -315,7 +315,7 @@ public class HashPartitionServiceTest { .setPartitions(isolatedQueue.getPartitions()) .build(); - partitionService_common.updateQueue(queueUpdateMsg); + partitionService_common.updateQueues(List.of(queueUpdateMsg)); partitionService_common.recalculatePartitions(commonRuleEngine, List.of(dedicatedRuleEngine)); // expecting event about no partitions for isolated queue key verifyPartitionChangeEvent(event -> { @@ -323,7 +323,7 @@ public class HashPartitionServiceTest { return event.getPartitionsMap().get(queueKey).isEmpty(); }); - partitionService_dedicated.updateQueue(queueUpdateMsg); + partitionService_dedicated.updateQueues(List.of(queueUpdateMsg)); partitionService_dedicated.recalculatePartitions(dedicatedRuleEngine, List.of(commonRuleEngine)); verifyPartitionChangeEvent(event -> { QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, tenantId); @@ -342,7 +342,7 @@ public class HashPartitionServiceTest { .setQueueIdLSB(isolatedQueue.getUuidId().getLeastSignificantBits()) .setQueueName(isolatedQueue.getName()) .build(); - partitionService_dedicated.removeQueue(queueDeleteMsg); + partitionService_dedicated.removeQueues(List.of(queueDeleteMsg)); verifyPartitionChangeEvent(event -> { QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.MAIN_QUEUE_NAME, tenantId); return event.getPartitionsMap().get(queueKey).isEmpty(); diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java index 233839bb5f..57bf94fec3 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java @@ -40,6 +40,7 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import java.util.List; import java.util.UUID; import static org.mockito.ArgumentMatchers.any; @@ -92,7 +93,7 @@ public class DefaultTbClusterServiceTest { when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer); - clusterService.onQueueChange(createTestQueue()); + clusterService.onQueuesUpdate(List.of(createTestQueue())); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH); verify(topicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any()); @@ -117,7 +118,7 @@ public class DefaultTbClusterServiceTest { when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer); - clusterService.onQueueChange(createTestQueue()); + clusterService.onQueuesUpdate(List.of(createTestQueue())); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2); @@ -145,7 +146,7 @@ public class DefaultTbClusterServiceTest { when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer); when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer); - clusterService.onQueueChange(createTestQueue()); + clusterService.onQueuesUpdate(List.of(createTestQueue())); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT); @@ -191,7 +192,7 @@ public class DefaultTbClusterServiceTest { when(producerProvider.getTbCoreNotificationsMsgProducer()).thenReturn(tbCoreQueueProducer); when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer); - clusterService.onQueueChange(createTestQueue()); + clusterService.onQueuesUpdate(List.of(createTestQueue())); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1); verify(topicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2); diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java index c852a24fa7..49017df678 100644 --- a/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueClusterService.java @@ -17,8 +17,12 @@ package org.thingsboard.server.queue; import org.thingsboard.server.common.data.queue.Queue; +import java.util.List; + public interface TbQueueClusterService { - void onQueueChange(Queue queue); - void onQueueDelete(Queue queue); + void onQueuesUpdate(List queues); + + void onQueuesDelete(List queues); + } diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index fea4848654..b1362bf27c 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -1280,8 +1280,8 @@ message ToCoreNotificationMsg { FromDeviceRPCResponseProto fromDeviceRpcResponse = 2; bytes componentLifecycleMsg = 3 [deprecated = true]; bytes edgeEventUpdateMsg = 4 [deprecated = true]; - QueueUpdateMsg queueUpdateMsg = 5; - QueueDeleteMsg queueDeleteMsg = 6; + repeated QueueUpdateMsg queueUpdateMsgs = 5; + repeated QueueDeleteMsg queueDeleteMsgs = 6; VersionControlResponseMsg vcResponseMsg = 7; bytes toEdgeSyncRequestMsg = 8 [deprecated = true]; bytes fromEdgeSyncResponseMsg = 9 [deprecated = true]; @@ -1307,8 +1307,8 @@ message ToRuleEngineMsg { message ToRuleEngineNotificationMsg { bytes componentLifecycleMsg = 1 [deprecated = true]; FromDeviceRPCResponseProto fromDeviceRpcResponse = 2; - QueueUpdateMsg queueUpdateMsg = 3; - QueueDeleteMsg queueDeleteMsg = 4; + repeated QueueUpdateMsg queueUpdateMsgs = 3; + repeated QueueDeleteMsg queueDeleteMsgs = 4; ComponentLifecycleMsgProto componentLifecycle = 5; } @@ -1328,8 +1328,8 @@ message ToTransportMsg { ResourceUpdateMsg resourceUpdateMsg = 12; ResourceDeleteMsg resourceDeleteMsg = 13; UplinkNotificationMsg uplinkNotificationMsg = 14; - QueueUpdateMsg queueUpdateMsg = 15; - QueueDeleteMsg queueDeleteMsg = 16; + repeated QueueUpdateMsg queueUpdateMsgs = 15; + repeated QueueDeleteMsg queueDeleteMsgs = 16; } message UsageStatsKVProto{ diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index bfc50e076b..acb26842d5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -171,27 +171,36 @@ public class HashPartitionService implements PartitionService { } @Override - public void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg) { - TenantId tenantId = new TenantId(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB())); - QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueUpdateMsg.getQueueName(), tenantId); - partitionTopicsMap.put(queueKey, queueUpdateMsg.getQueueTopic()); - partitionSizesMap.put(queueKey, queueUpdateMsg.getPartitions()); - myPartitions.remove(queueKey); - if (!tenantId.isSysTenantId()) { - tenantRoutingInfoMap.remove(tenantId); + public void updateQueues(List queueUpdateMsgs) { + for (TransportProtos.QueueUpdateMsg queueUpdateMsg : queueUpdateMsgs) { + TenantId tenantId = TenantId.fromUUID(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB())); + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueUpdateMsg.getQueueName(), tenantId); + partitionTopicsMap.put(queueKey, queueUpdateMsg.getQueueTopic()); + partitionSizesMap.put(queueKey, queueUpdateMsg.getPartitions()); + myPartitions.remove(queueKey); + if (!tenantId.isSysTenantId()) { + tenantRoutingInfoMap.remove(tenantId); + } } } @Override - public void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg) { - TenantId tenantId = new TenantId(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); - QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); - myPartitions.remove(queueKey); - partitionTopicsMap.remove(queueKey); - partitionSizesMap.remove(queueKey); - evictTenantInfo(tenantId); + public void removeQueues(List queueDeleteMsgs) { + List queueKeys = queueDeleteMsgs.stream() + .map(queueDeleteMsg -> { + TenantId tenantId = TenantId.fromUUID(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); + return new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); + }) + .collect(Collectors.toList()); + queueKeys.forEach(queueKey -> { + myPartitions.remove(queueKey); + partitionTopicsMap.remove(queueKey); + partitionSizesMap.remove(queueKey); + evictTenantInfo(queueKey.getTenantId()); + }); if (serviceInfoProvider.isService(ServiceType.TB_RULE_ENGINE)) { - publishPartitionChangeEvent(ServiceType.TB_RULE_ENGINE, Map.of(queueKey, Collections.emptySet())); + publishPartitionChangeEvent(ServiceType.TB_RULE_ENGINE, queueKeys.stream() + .collect(Collectors.toMap(k -> k, k -> Collections.emptySet()))); } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java index e9049a8085..27417e5f20 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java @@ -63,9 +63,9 @@ public interface PartitionService { int countTransportsByType(String type); - void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg); + void updateQueues(List queueUpdateMsgs); - void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg); + void removeQueues(List queueDeleteMsgs); void removeTenant(TenantId tenantId); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 6f962ca269..2d6ab0fb8b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -50,9 +50,9 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.limit.LimitedApi; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -96,10 +96,9 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.AsyncCallbackTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.QueueKey; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; @@ -1056,10 +1055,10 @@ public class DefaultTransportService implements TransportService { log.warn("ResourceDelete - [{}] [{}]", id, mdRez); transportCallbackExecutor.submit(() -> mdRez.getListener().onResourceDelete(msg)); }); - } else if (toSessionMsg.hasQueueUpdateMsg()) { - partitionService.updateQueue(toSessionMsg.getQueueUpdateMsg()); - } else if (toSessionMsg.hasQueueDeleteMsg()) { - partitionService.removeQueue(toSessionMsg.getQueueDeleteMsg()); + } else if (toSessionMsg.getQueueUpdateMsgsCount() > 0) { + partitionService.updateQueues(toSessionMsg.getQueueUpdateMsgsList()); + } else if (toSessionMsg.getQueueDeleteMsgsCount() > 0) { + partitionService.removeQueues(toSessionMsg.getQueueDeleteMsgsList()); } else { //TODO: should we notify the device actor about missed session? log.debug("[{}] Missing session.", sessionId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java index c1b0c66875..1bf9e25fc0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java @@ -57,9 +57,6 @@ public class BaseQueueService extends AbstractEntityService implements QueueServ @Autowired private DataValidator queueValidator; -// @Autowired -// private QueueStatsService queueStatsService; - @Override public Queue saveQueue(Queue queue) { log.trace("Executing createOrUpdateQueue [{}]", queue); From 167d8758f6c1d16f6e45945ac13be7495e406913 Mon Sep 17 00:00:00 2001 From: artem Date: Wed, 24 Jan 2024 18:28:17 +0200 Subject: [PATCH 044/137] TbGpsGeofencingActionNode: renamed key to reportPresenceStatusOnEachMessage + removed told variable + tests refactored --- .../engine/geo/TbGpsGeofencingActionNode.java | 46 ++++--- ...bGpsGeofencingActionNodeConfiguration.java | 4 +- .../geo/GpsGeofencingActionTestCase.java | 38 ++++++ .../geo/TbGpsGeofencingActionNodeTest.java | 117 ++++++++---------- 4 files changed, 120 insertions(+), 85 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java index cd88a1eda7..0a9d0deb1d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java @@ -62,14 +62,16 @@ import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; "

" + "If an object with coordinates extracted from incoming message enters the geofence, sends a message with the type Entered. " + "If an object leaves the geofence, sends a message with the type Left. " + - "If the presence monitoring strategy \"On first message\" is selected, sends messages with types Inside or Outside only the first time the geofencing and duration conditions are satisfied; otherwise Success. " + - "If the presence monitoring strategy \"On each message\" is selected, sends messages with types Inside or Outside every time the geofencing condition is satisfied.", + "If the presence monitoring strategy \"On first message\" is selected, sends messages via rule node connection type Inside or Outside only the first time the geofencing and duration conditions are satisfied; otherwise sends messages via rule node connection type Success. " + + "If the presence monitoring strategy \"On each message\" is selected, sends messages via rule node connection type Inside or Outside every time the geofencing condition is satisfied. " + + "

" + + "Output connections: Entered, Left, Inside, Outside, Success", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGpsGeofencingConfig" ) public class TbGpsGeofencingActionNode extends AbstractGeofencingNode { - private static final String PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE = "presenceMonitoringStrategyOnEachMessage"; + private static final String REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE = "reportPresenceStatusOnEachMessage"; private final Map entityStates = new HashMap<>(); private final Gson gson = new Gson(); private final JsonParser parser = new JsonParser(); @@ -95,28 +97,32 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode (entityState.isInside() ? - TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { - setStaid(ctx, msg.getOriginator(), entityState); - ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); - told = true; - } - } - } else { + return; + } + + if (config.isReportPresenceStatusOnEachMessage()) { ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); - told = true; + return; } - if (!told) { + + if (entityState.isStayed()) { ctx.tellSuccess(msg); + return; } + + long stayTime = ts - entityState.getStateSwitchTime(); + if (stayTime > (entityState.isInside() ? + TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : + TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { + setStaid(ctx, msg.getOriginator(), entityState); + ctx.tellNext(msg, entityState.isInside() ? INSIDE : OUTSIDE); + return; + } + + ctx.tellSuccess(msg); } private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) { @@ -150,9 +156,9 @@ public class TbGpsGeofencingActionNode extends AbstractGeofencingNode upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { boolean hasChanges = false; if (fromVersion == 0) { - if (!oldConfiguration.has(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE)) { + if (!oldConfiguration.has(REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE)) { hasChanges = true; - ((ObjectNode) oldConfiguration).put(PRESENCE_MONITORING_STRATEGY_ON_EACH_MESSAGE, false); + ((ObjectNode) oldConfiguration).put(REPORT_PRESENCE_STATUS_ON_EACH_MESSAGE, false); } } return new TbPair<>(hasChanges, oldConfiguration); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java index ee375083b4..04bbab01b7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeConfiguration.java @@ -31,7 +31,7 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte private String minInsideDurationTimeUnit; private String minOutsideDurationTimeUnit; - private boolean presenceMonitoringStrategyOnEachMessage; + private boolean reportPresenceStatusOnEachMessage; @Override public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { @@ -45,7 +45,7 @@ public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilte configuration.setMinOutsideDurationTimeUnit(TimeUnit.MINUTES.name()); configuration.setMinInsideDuration(1); configuration.setMinOutsideDuration(1); - configuration.setPresenceMonitoringStrategyOnEachMessage(false); + configuration.setReportPresenceStatusOnEachMessage(true); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java new file mode 100644 index 0000000000..8444c9cf0d --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java @@ -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. + */ +package org.thingsboard.rule.engine.geo; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class GpsGeofencingActionTestCase { + private EntityId entityId; + private Map entityStates; + private boolean msgInside; + private boolean reportPresenceStatusOnEachMessage; + + public GpsGeofencingActionTestCase(EntityId entityId, boolean msgInside, boolean reportPresenceStatusOnEachMessage, EntityGeofencingState entityGeofencingState) { + this.entityId = entityId; + this.msgInside = msgInside; + this.reportPresenceStatusOnEachMessage = reportPresenceStatusOnEachMessage; + this.entityStates = new HashMap<>(); + this.entityStates.put(entityId, entityGeofencingState); + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java index dd7536f4ee..42d69f2593 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -37,8 +38,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; -import java.util.List; -import java.util.Map; +import java.time.Duration; import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; @@ -51,6 +51,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.INSIDE; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT; +import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; class TbGpsGeofencingActionNodeTest { @@ -70,42 +72,47 @@ class TbGpsGeofencingActionNodeTest { node.destroy(); } - private static Stream givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes() { + private static Stream givenReportPresenceStatusOnEachMessage_whenOnMsg_thenVerifyOutputMsgType() { + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + long tsNow = System.currentTimeMillis(); + long tsNowMinusMinuteAndMillis = tsNow - Duration.ofMinutes(1).plusMillis(1).toMillis(); return Stream.of( - // default config with presenceMonitoringStrategyOnEachMessage false - Arguments.of(false, List.of( - Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 1), - Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 1), - Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 2) - )), - // default config with presenceMonitoringStrategyOnEachMessage true - Arguments.of(true, List.of( - Map.of(ENTERED, 0, INSIDE, 0, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 0, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 1, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 2, SUCCESS, 0), - Map.of(ENTERED, 1, INSIDE, 3, SUCCESS, 0) - )) + // default config with presenceMonitoringStrategyOnEachMessage false and msgInside true + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNow, true)), SUCCESS), + // default config with presenceMonitoringStrategyOnEachMessage false and msgInside false + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNow, true)), SUCCESS), + // default config with presenceMonitoringStrategyOnEachMessage true and msgInside true + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(true, tsNow, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + // default config with presenceMonitoringStrategyOnEachMessage true and msgInside false + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, tsNow, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE) ); } @ParameterizedTest @MethodSource - void givenPresenceMonitoringStrategyOnEachMessage_whenOnMsg_thenVerifyOutputMsgTypes( - boolean presenceMonitoringStrategyOnEachMessage, - List> outputMsgTypesCountList + void givenReportPresenceStatusOnEachMessage_whenOnMsg_thenVerifyOutputMsgType( + GpsGeofencingActionTestCase gpsGeofencingActionTestCase, + String expectedOutput ) throws TbNodeException { // GIVEN var config = new TbGpsGeofencingActionNodeConfiguration().defaultConfiguration(); - config.setPresenceMonitoringStrategyOnEachMessage(presenceMonitoringStrategyOnEachMessage); + config.setReportPresenceStatusOnEachMessage(gpsGeofencingActionTestCase.isReportPresenceStatusOnEachMessage()); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - TbMsgMetaData metadata = getMetadataForNewVersionPolygonPerimeter(); - TbMsg msg = getTbMsg(deviceId, metadata, - GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + TbMsg msg = gpsGeofencingActionTestCase.isMsgInside() ? + getInsideRectangleTbMsg(gpsGeofencingActionTestCase.getEntityId()) : + getOutsideRectangleTbMsg(gpsGeofencingActionTestCase.getEntityId()); when(ctx.getAttributesService()).thenReturn(attributesService); when(ctx @@ -113,40 +120,30 @@ class TbGpsGeofencingActionNodeTest { .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getServiceId())) .thenReturn(Futures.immediateFuture(Optional.empty())); - // WHEN - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - node.onMsg(ctx, msg); - - // THEN - verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(0)); + ReflectionTestUtils.setField(node, "entityStates", gpsGeofencingActionTestCase.getEntityStates()); // WHEN - msg = getTbMsg(deviceId, metadata, - GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); - node.onMsg(ctx, msg); - - // THEN - verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(1)); - - // WHEN - node.onMsg(ctx, msg); - - // THEN - verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(2)); - - // WHEN - config.setMinInsideDuration(0); - node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); node.onMsg(ctx, msg); // THEN - verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(3)); + if (SUCCESS.equals(expectedOutput)) { + verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + } else { + verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(expectedOutput)); + } + } - // WHEN - node.onMsg(ctx, msg); + private TbMsg getOutsideRectangleTbMsg(EntityId entityId) { + return getTbMsg(entityId, getMetadataForNewVersionPolygonPerimeter(), + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLatitude(), + GeoUtilTest.POINT_OUTSIDE_SIMPLE_RECT.getLongitude()); + } - // THEN - verifyNodeOutputs(newMsgCaptor, outputMsgTypesCountList.get(4)); + private TbMsg getInsideRectangleTbMsg(EntityId entityId) { + return getTbMsg(entityId, getMetadataForNewVersionPolygonPerimeter(), + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLatitude(), + GeoUtilTest.POINT_INSIDE_SIMPLE_RECT_CENTER.getLongitude()); } private TbMsg getTbMsg(EntityId entityId, TbMsgMetaData metadata, double latitude, double longitude) { @@ -160,12 +157,6 @@ class TbGpsGeofencingActionNodeTest { return metadata; } - private void verifyNodeOutputs(ArgumentCaptor newMsgCaptor, Map outputMsgTypesCount) { - verify(this.ctx, times(outputMsgTypesCount.get(ENTERED))).tellNext(newMsgCaptor.capture(), eq(ENTERED)); - verify(this.ctx, times(outputMsgTypesCount.get(INSIDE))).tellNext(newMsgCaptor.capture(), eq(INSIDE)); - verify(this.ctx, times(outputMsgTypesCount.get(SUCCESS))).tellSuccess(newMsgCaptor.capture()); - } - // Rule nodes upgrade private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( @@ -193,7 +184,7 @@ class TbGpsGeofencingActionNodeTest { " \"minOutsideDuration\": 1,\n" + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + - " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + " \"latitudeKeyName\": \"latitude\",\n" + " \"longitudeKeyName\": \"longitude\",\n" + " \"perimeterType\": \"POLYGON\",\n" + @@ -212,7 +203,7 @@ class TbGpsGeofencingActionNodeTest { " \"minOutsideDuration\": 1,\n" + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + - " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + " \"latitudeKeyName\": \"latitude\",\n" + " \"longitudeKeyName\": \"longitude\",\n" + " \"perimeterType\": \"POLYGON\",\n" + @@ -230,7 +221,7 @@ class TbGpsGeofencingActionNodeTest { " \"minOutsideDuration\": 1,\n" + " \"minInsideDurationTimeUnit\": \"MINUTES\",\n" + " \"minOutsideDurationTimeUnit\": \"MINUTES\",\n" + - " \"presenceMonitoringStrategyOnEachMessage\": false,\n" + + " \"reportPresenceStatusOnEachMessage\": false,\n" + " \"latitudeKeyName\": \"latitude\",\n" + " \"longitudeKeyName\": \"longitude\",\n" + " \"perimeterType\": \"POLYGON\",\n" + From 54203ea1775ac91c402362e975babfb003f5552e Mon Sep 17 00:00:00 2001 From: artem Date: Wed, 24 Jan 2024 19:19:10 +0200 Subject: [PATCH 045/137] TbGpsGeofencingActionNodeTest: added asserts and additional verifications --- .../geo/TbGpsGeofencingActionNodeTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java index 42d69f2593..d61688d5a9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -44,8 +44,10 @@ import java.util.UUID; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -123,15 +125,27 @@ class TbGpsGeofencingActionNodeTest { ReflectionTestUtils.setField(node, "entityStates", gpsGeofencingActionTestCase.getEntityStates()); // WHEN - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); node.onMsg(ctx, msg); // THEN + verify(ctx, never()).tellFailure(any(), any(Throwable.class)); + verify(ctx, never()).enqueueForTellNext(any(), eq(expectedOutput), any(), any()); + verify(ctx, never()).ack(any()); + + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); if (SUCCESS.equals(expectedOutput)) { verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); } else { verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(expectedOutput)); } + + var actualMsg = newMsgCaptor.getValue(); + assertThat(actualMsg).isNotNull(); + assertThat(actualMsg).isSameAs(msg); + assertThat(actualMsg.getType()).isSameAs(msg.getType()); + assertThat(actualMsg.getData()).isSameAs(msg.getData()); + assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData()); + assertThat(actualMsg.getOriginator()).isSameAs(msg.getOriginator()); } private TbMsg getOutsideRectangleTbMsg(EntityId entityId) { From 27f448a5430220ca8c6acb41449cc7cdcd4496ec Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 25 Jan 2024 12:04:18 +0200 Subject: [PATCH 046/137] Improve partition service logs --- .../queue/discovery/HashPartitionService.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index acb26842d5..2d0e03ecf9 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -325,13 +325,11 @@ public class HashPartitionService implements PartitionService { .forEach(removed::add); } removed.forEach(queueKey -> { - log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", queueKey); changedPartitionsMap.put(queueKey, Collections.emptySet()); }); myPartitions.forEach((queueKey, partitions) -> { if (!partitions.equals(oldPartitions.get(queueKey))) { - log.info("[{}] NEW PARTITIONS: {}", queueKey, partitions); Set tpiList = partitions.stream() .map(partition -> buildTopicPartitionInfo(queueKey, partition)) .collect(Collectors.toSet()); @@ -377,14 +375,11 @@ public class HashPartitionService implements PartitionService { } private void publishPartitionChangeEvent(ServiceType serviceType, Map> partitionsMap) { - if (log.isDebugEnabled()) { - log.debug("Publishing partition change event for service type " + serviceType + ":" + System.lineSeparator() + - partitionsMap.entrySet().stream() - .map(entry -> entry.getKey() + " - " + entry.getValue().stream() - .map(TopicPartitionInfo::getFullTopicName).sorted() - .collect(Collectors.toList())) - .collect(Collectors.joining(System.lineSeparator()))); - } + log.info("Partitions changed: {}", System.lineSeparator() + partitionsMap.entrySet().stream() + .map(entry -> "[" + entry.getKey() + "] - [" + entry.getValue().stream() + .map(tpi -> tpi.getPartition().orElse(-1).toString()).sorted() + .collect(Collectors.joining(", ")) + "]") + .collect(Collectors.joining(System.lineSeparator()))); applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceType, partitionsMap)); } @@ -489,7 +484,7 @@ public class HashPartitionService implements PartitionService { } private void logServiceInfo(TransportProtos.ServiceInfo server) { - log.info("[{}] Found common server: [{}]", server.getServiceId(), server.getServiceTypesList()); + log.info("[{}] Found common server: {}", server.getServiceId(), server.getServiceTypesList()); } private void addNode(Map> queueServiceList, ServiceInfo instance) { From d7a24648ff7ad463461552e317f67305c1460224 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 25 Jan 2024 12:11:53 +0200 Subject: [PATCH 047/137] Add stats for connect, disconnect and inactivity events --- .../queue/DefaultTbCoreConsumerService.java | 9 +++++++ .../service/queue/TbCoreConsumerStats.java | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 3a0e0e76d8..41f7cc012c 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -660,6 +660,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Wed, 24 Jan 2024 11:28:28 +0100 Subject: [PATCH 048/137] Support DTLS Connection ID with configuration --- .../src/main/resources/thingsboard.yml | 2 + .../LwM2MTransportBootstrapService.java | 2 + .../config/LwM2MTransportServerConfig.java | 4 + .../server/DefaultLwM2mTransportService.java | 2 + .../LwM2MTransportBootstrapServiceTest.java | 105 +++++++++++++++++ .../LwM2MTransportServerConfigTest.java | 61 ++++++++++ .../DefaultLwM2mTransportServiceTest.java | 109 ++++++++++++++++++ .../src/main/resources/tb-lwm2m-transport.yml | 2 + 8 files changed, 287 insertions(+) create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfigTest.java create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportServiceTest.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 936f30af60..395eefeb45 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1044,6 +1044,8 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" + # "" disables connection id support, 0 enables support but not for incoming traffic, any value greater than 0 set the connection id size in bytes + connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:6}" server: # LwM2M Server ID id: "${LWM2M_SERVER_ID:123}" diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 4bdcbcf645..a9166b0bf1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -38,6 +38,7 @@ import javax.annotation.PreDestroy; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; @@ -95,6 +96,7 @@ public class LwM2MTransportBootstrapService { dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS); + dtlsConfig.set(DTLS_CONNECTION_ID_LENGTH, serverConfig.getDtlsConnectionIdLength()); dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); setServerWithCredentials(builder, dtlsConfig); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index 0ccf6e1969..dc2e9f6d45 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -41,6 +41,10 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.dtls.retransmission_timeout:9000}") private int dtlsRetransmissionTimeout; + @Getter + @Value("${transport.lwm2m.dtls.connection_id_length:6}") + private Integer dtlsConnectionIdLength; + @Getter @Value("${transport.lwm2m.timeout:}") private Long timeout; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 4037251572..7d54e97cde 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -43,6 +43,7 @@ import javax.annotation.PreDestroy; import java.security.cert.X509Certificate; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CONNECTION_ID_LENGTH; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT; @@ -139,6 +140,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups()); dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers()); dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS); + dtlsConfig.set(DTLS_CONNECTION_ID_LENGTH, config.getDtlsConnectionIdLength()); dtlsConfig.set(DTLS_ROLE, SERVER_ONLY); /* Create credentials */ diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java new file mode 100644 index 0000000000..23293c7b7a --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServiceTest.java @@ -0,0 +1,105 @@ +/** + * 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.transport.lwm2m.bootstrap; + +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; +import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.TbLwM2MDtlsBootstrapCertificateVerifier; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; +import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.when; + +@ExtendWith(MockitoExtension.class) +public class LwM2MTransportBootstrapServiceTest { + + @Mock + private LwM2MTransportServerConfig serverConfig; + @Mock + private LwM2MTransportBootstrapConfig bootstrapConfig; + @Mock + private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; + @Mock + private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; + @Mock + private TransportService transportService; + @Mock + private TbLwM2MDtlsBootstrapCertificateVerifier certificateVerifier; + + + @Test + public void getLHServer_creates_ConnectionIdGenerator_when_connection_id_length_not_null(){ + final Integer CONNECTION_ID_LENGTH = 6; + when(serverConfig.getDtlsConnectionIdLength()).thenReturn(CONNECTION_ID_LENGTH); + var lwM2MBootstrapService = createLwM2MBootstrapService(); + + var server = lwM2MBootstrapService.getLhBootstrapServer(); + var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint"); + assertThat(securedEndpoint).isNotNull(); + + var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config"); + assertThat(config).isNotNull(); + assertThat(config.getConnectionIdGenerator()).isNotNull(); + assertThat((Integer) ReflectionTestUtils.getField(config.getConnectionIdGenerator(), "connectionIdLength")) + .isEqualTo(CONNECTION_ID_LENGTH); + } + + @Test + public void getLHServer_creates_no_ConnectionIdGenerator_when_connection_id_length_is_null(){ + when(serverConfig.getDtlsConnectionIdLength()).thenReturn(null); + var lwM2MBootstrapService = createLwM2MBootstrapService(); + + var server = lwM2MBootstrapService.getLhBootstrapServer(); + var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint"); + assertThat(securedEndpoint).isNotNull(); + + var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config"); + assertThat(config).isNotNull(); + assertThat(config.getConnectionIdGenerator()).isNull(); + } + + private LwM2MTransportBootstrapService createLwM2MBootstrapService() { + setDefaultConfigVariables(); + return new LwM2MTransportBootstrapService(serverConfig, bootstrapConfig, lwM2MBootstrapSecurityStore, + lwM2MInMemoryBootstrapConfigStore, transportService, certificateVerifier); + } + + private void setDefaultConfigVariables(){ + when(bootstrapConfig.getPort()).thenReturn(5683); + when(bootstrapConfig.getSecurePort()).thenReturn(5684); + when(serverConfig.isRecommendedCiphers()).thenReturn(false); + when(serverConfig.getDtlsRetransmissionTimeout()).thenReturn(9000); + } + + +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfigTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfigTest.java new file mode 100644 index 0000000000..7e657dd932 --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfigTest.java @@ -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. + */ +package org.thingsboard.server.transport.lwm2m.config; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootContextLoader; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties(value = LwM2MTransportServerConfig.class) +@ContextConfiguration(classes = {LwM2MTransportServerConfig.class}, loader = SpringBootContextLoader.class) +@TestPropertySource(properties = { + "transport.sessions.report_timeout=10", + "transport.lwm2m.security.recommended_ciphers=true", + "transport.lwm2m.security.recommended_supported_groups=true", + "transport.lwm2m.downlink_pool_size=10", + "transport.lwm2m.uplink_pool_size=10", + "transport.lwm2m.ota_pool_size=10", + "transport.lwm2m.clean_period_in_sec=2", + "transport.lwm2m.dtls.connection_id_length=" + +}) +class LwM2MTransportServerConfigTest { + + @MockBean(name = "lwm2mServerCredentials") + private SslCredentialsConfig credentialsConfig; + + @MockBean(name = "lwm2mTrustCredentials") + private SslCredentialsConfig trustCredentialsConfig; + + @Autowired + private LwM2MTransportServerConfig serverConfig; + + @Test + void getDtlsConnectionIdLength_return_null_is_property_is_empty() { + // note: transport.lwm2m.dtls.connect_id_length is set in TestPropertySource + assertThat(serverConfig.getDtlsConnectionIdLength()).isNull(); + } +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportServiceTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportServiceTest.java new file mode 100644 index 0000000000..92f60ebb8e --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportServiceTest.java @@ -0,0 +1,109 @@ +/** + * 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.transport.lwm2m.server; + +import org.eclipse.californium.core.network.CoapEndpoint; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.leshan.server.californium.LeshanServer; +import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; +import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.when; + +@ExtendWith(MockitoExtension.class) +public class DefaultLwM2mTransportServiceTest { + + @Mock + private LwM2mTransportContext context; + + @Mock + private LwM2MTransportServerConfig config; + @Mock + private OtaPackageDataCache otaPackageDataCache; + @Mock + private LwM2mUplinkMsgHandler handler; + @Mock + private CaliforniumRegistrationStore registrationStore; + @Mock + private TbSecurityStore securityStore; + @Mock + private TbLwM2MDtlsCertificateVerifier certificateVerifier; + @Mock + private TbLwM2MAuthorizer authorizer; + @Mock + private LwM2mVersionedModelProvider modelProvider; + + + @Test + public void getLHServer_creates_ConnectionIdGenerator_when_connection_id_length_not_null(){ + final Integer CONNECTION_ID_LENGTH = 6; + when(config.getDtlsConnectionIdLength()).thenReturn(CONNECTION_ID_LENGTH); + var lwm2mService = createLwM2MService(); + + LeshanServer server = ReflectionTestUtils.invokeMethod(lwm2mService, "getLhServer"); + + assertThat(server).isNotNull(); + var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint"); + assertThat(securedEndpoint).isNotNull(); + + var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config"); + assertThat(config).isNotNull(); + assertThat(config.getConnectionIdGenerator()).isNotNull(); + assertThat((Integer) ReflectionTestUtils.getField(config.getConnectionIdGenerator(), "connectionIdLength")) + .isEqualTo(CONNECTION_ID_LENGTH); + } + + @Test + public void getLHServer_creates_no_ConnectionIdGenerator_when_connection_id_length_is_null(){ + when(config.getDtlsConnectionIdLength()).thenReturn(null); + var lwm2mService = createLwM2MService(); + + LeshanServer server = ReflectionTestUtils.invokeMethod(lwm2mService, "getLhServer"); + + assertThat(server).isNotNull(); + var securedEndpoint = (CoapEndpoint) ReflectionTestUtils.getField(server, "securedEndpoint"); + assertThat(securedEndpoint).isNotNull(); + var config = (DtlsConnectorConfig) ReflectionTestUtils.getField(securedEndpoint.getConnector(), "config"); + assertThat(config).isNotNull(); + assertThat(config.getConnectionIdGenerator()).isNull(); + } + + private DefaultLwM2mTransportService createLwM2MService() { + setDefaultConfigVariables(); + return new DefaultLwM2mTransportService(context, config, otaPackageDataCache, handler, registrationStore, + securityStore, certificateVerifier, authorizer, modelProvider); + } + + private void setDefaultConfigVariables(){ + when(config.getPort()).thenReturn(5683); + when(config.getSecurePort()).thenReturn(5684); + when(config.isRecommendedCiphers()).thenReturn(false); + when(config.getDtlsRetransmissionTimeout()).thenReturn(9000); + } + + +} \ No newline at end of file diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 688392d264..b6dc7248ab 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -155,6 +155,8 @@ transport: dtls: # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000 retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}" + # "" disables connection id support, 0 enables support but not for incoming traffic, any value greater than 0 set the connection id size in bytes + connection_id_length: "${LWM2M_DTLS_CONNECTION_ID_LENGTH:6}" server: # LwM2M Server ID id: "${LWM2M_SERVER_ID:123}" From 7bd55e5838fa3d2c939778a5ff8f4ec513967783 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Thu, 25 Jan 2024 12:20:40 +0200 Subject: [PATCH 049/137] Fixed toast popup position after Gateway connector saving --- .../widget/lib/gateway/gateway-connectors.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index a9465fc65b..f47bb748a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -366,7 +366,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie type: 'success', duration: 1000, verticalPosition: 'top', - horizontalPosition: 'right', + horizontalPosition: 'left', target: 'dashboardRoot', forceDismiss: true })); From e0348751ad5e8514134f7d60ab45d86d247ceb63 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 25 Jan 2024 12:58:27 +0200 Subject: [PATCH 050/137] UI: added html reponse improvement for snmp transport config --- ...ice-profile-communication-config.component.html | 2 +- ...ice-profile-communication-config.component.scss | 14 ++++++++++++++ .../snmp-device-profile-mapping.component.html | 2 +- .../snmp-device-profile-mapping.component.scss | 10 +++------- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html index 1b1d97ae06..de80b9f238 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.html @@ -19,7 +19,7 @@
-
+
device-profile.snmp.scope diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss index 027e82c89c..d3908cb6bf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss @@ -19,6 +19,13 @@ border-radius: 4px; padding: 8px; min-width: 0; + flex-direction: row; + box-sizing: border-box; + display: flex; + place-content: stretch flex-start; + align-items: stretch; + flex: 1 1 0; + gap: 8px; } .scope-row { @@ -28,6 +35,13 @@ .required-text { margin: 16px 0 } + + @media (max-width: 1500px) { + .communication-config { + flex-direction: column; + gap: 0; + } + } } :host ::ng-deep { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html index f56bfa9255..94330f7c23 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html @@ -28,7 +28,7 @@
-
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.scss index d9ad3d7923..01ff5a71e2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.scss @@ -15,13 +15,9 @@ */ :host { .mapping-config { - min-width: 518px; - } - .mapping-list { - padding-bottom: 8px; - } - .required-text { - margin: 14px 0; + .required-text { + margin: 14px 0; + } } } From 2c07a5ea929fd97471ab80ee6ed5a98130b9e13c Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 25 Jan 2024 13:23:12 +0200 Subject: [PATCH 051/137] minor edition for media query --- .../snmp-device-profile-communication-config.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss index d3908cb6bf..870b627378 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss @@ -36,7 +36,7 @@ margin: 16px 0 } - @media (max-width: 1500px) { + @media (max-width: 1650px) { .communication-config { flex-direction: column; gap: 0; From fec8c5695ddcc332538be6bae1ba8b464f40bf96 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 25 Jan 2024 13:32:33 +0200 Subject: [PATCH 052/137] UI: minor improvements for styles and timewindow scss --- .../app/shared/components/time/timewindow.component.scss | 6 ++++++ ui-ngx/src/styles.scss | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.scss b/ui-ngx/src/app/shared/components/time/timewindow.component.scss index 92673a41ad..d793bc5367 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -58,6 +58,12 @@ .mdc-button__label { overflow: hidden; text-overflow: ellipsis; + height: 100%; + + span { + display: block; + height: 100%; + } } } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 0750de263f..8ca9fe4e37 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -1252,4 +1252,8 @@ mat-icon { .cursor-pointer { cursor: pointer; } + + .no-wrap { + white-space: nowrap; + } } From 2f7351200504126c159cb7ef920eb447c8ce7f36 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 25 Jan 2024 15:47:26 +0200 Subject: [PATCH 053/137] Don't remove from myPartitions on queue update --- .../thingsboard/server/queue/discovery/HashPartitionService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index 2d0e03ecf9..9b4cf0e974 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -177,7 +177,6 @@ public class HashPartitionService implements PartitionService { QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueUpdateMsg.getQueueName(), tenantId); partitionTopicsMap.put(queueKey, queueUpdateMsg.getQueueTopic()); partitionSizesMap.put(queueKey, queueUpdateMsg.getPartitions()); - myPartitions.remove(queueKey); if (!tenantId.isSysTenantId()) { tenantRoutingInfoMap.remove(tenantId); } From 8f9eddabbae1ddd2b0dd4ea2868cc1361d5e93d0 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 25 Jan 2024 16:00:44 +0200 Subject: [PATCH 054/137] UI: added singletonMode to copy buffer of rule-node --- ui-ngx/src/app/core/services/item-buffer.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index eeade82898..b928245765 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -27,7 +27,7 @@ import { widgetType } from '@shared/models/widget.models'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; -import { deepClone, isEqual } from '@core/utils'; +import { deepClone, isDefinedAndNotNull, isEqual } from '@core/utils'; import { UtilsService } from '@core/services/utils.service'; import { Observable, of, throwError } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -309,6 +309,9 @@ export class ItemBufferService { if (origNode.error) { node.error = origNode.error; } + if (isDefinedAndNotNull(origNode.singletonMode)) { + node.singletonMode = origNode.singletonMode; + } ruleNodes.nodes.push(node); if (i === 0) { top = node.y; From b635e598857f49fa17384b0229785f1e24d45c4d Mon Sep 17 00:00:00 2001 From: Yevhen Popok Date: Fri, 26 Jan 2024 09:56:03 +0200 Subject: [PATCH 055/137] For Angular Gridster increase "maxRows" limit to "Infinity" --- .../modules/home/components/dashboard/dashboard.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 469630c212..6fa26ee412 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -211,7 +211,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo disableAutoPositionOnConflict: false, pushItems: false, swap: false, - maxRows: 100, + maxRows: Infinity, minCols: this.columns ? this.columns : 24, maxCols: 3000, maxItemCols: 1000, From 3ee8a8c56d10d5a08111032c6d90067d6a538535 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 26 Jan 2024 12:17:37 +0200 Subject: [PATCH 056/137] Refactor rate limit service --- .../config/RateLimitProcessingFilter.java | 2 +- .../server/controller/AuthController.java | 2 +- .../controller/plugin/TbWebSocketHandler.java | 2 +- .../DefaultNotificationCenter.java | 2 +- .../DefaultNotificationRuleProcessor.java | 2 +- .../auth/mfa/DefaultTwoFactorAuthService.java | 2 +- .../DefaultEntitiesExportImportService.java | 2 +- .../service/limits/RateLimitServiceTest.java | 10 ++++---- .../notification/NotificationRuleApiTest.java | 2 +- common/cache/pom.xml | 4 +++ .../limits/DefaultRateLimitService.java | 16 ++++++------ .../cache}/limits/RateLimitService.java | 2 +- .../cache/limits/TenantProfileProvider.java | 25 +++++++++++++++++++ .../CassandraBufferedRateReadExecutor.java | 2 +- .../CassandraBufferedRateWriteExecutor.java | 2 +- .../tenant/DefaultTbTenantProfileCache.java | 3 ++- .../util/AbstractBufferedRateExecutor.java | 2 +- 17 files changed, 55 insertions(+), 27 deletions(-) rename {dao/src/main/java/org/thingsboard/server/dao/util => common/cache/src/main/java/org/thingsboard/server/cache}/limits/DefaultRateLimitService.java (89%) rename {dao/src/main/java/org/thingsboard/server/dao/util => common/cache/src/main/java/org/thingsboard/server/cache}/limits/RateLimitService.java (95%) create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/limits/TenantProfileProvider.java diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java index 7791c7ee76..0b859e7b06 100644 --- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.TenantProfileNotFoundException; import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.security.model.SecurityUser; diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index ba4c2b1fbd..92441bf1cb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.security.event.UserSessionInvalidation import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.ActivateUserRequest; diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 1696d5c20b..64536ddb54 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -43,7 +43,7 @@ import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.config.WebSocketConfiguration; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider; import org.thingsboard.server.service.security.model.SecurityUser; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index be37d98bb5..11c40d964a 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -62,7 +62,7 @@ import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TopicService; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java index a68708d8ae..a86315ce86 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.notification.NotificationRequestService; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.notification.NotificationDeduplicationService; import org.thingsboard.server.service.executors.NotificationExecutorService; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java index b2ebcb0b5d..d4425372e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProvi import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.common.data.limit.LimitedApi; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index 08a59973de..4e3d034207 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult; import org.thingsboard.server.common.data.util.ThrowingRunnable; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; diff --git a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java index 9917e837b9..afdb38a2ec 100644 --- a/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/limits/RateLimitServiceTest.java @@ -20,6 +20,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.server.cache.limits.DefaultRateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.NotificationRuleId; @@ -28,9 +30,7 @@ import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.util.limits.DefaultRateLimitService; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.dao.tenant.DefaultTbTenantProfileCache; import java.util.List; import java.util.UUID; @@ -46,12 +46,12 @@ import static org.mockito.Mockito.when; public class RateLimitServiceTest { private RateLimitService rateLimitService; - private TbTenantProfileCache tenantProfileCache; + private DefaultTbTenantProfileCache tenantProfileCache; private TenantId tenantId; @Before public void beforeEach() { - tenantProfileCache = Mockito.mock(TbTenantProfileCache.class); + tenantProfileCache = Mockito.mock(DefaultTbTenantProfileCache.class); rateLimitService = new DefaultRateLimitService(tenantProfileCache, mock(NotificationRuleProcessor.class), 60, 100); tenantId = new TenantId(UUID.randomUUID()); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index ed4a1c613a..1e95f9add3 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -93,7 +93,7 @@ import org.thingsboard.server.dao.notification.DefaultNotifications; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.queue.notification.DefaultNotificationDeduplicationService; import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache; import org.thingsboard.server.service.state.DeviceStateService; diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 6b9544e761..184aa615ba 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -40,6 +40,10 @@ org.thingsboard.common data + + org.thingsboard.common + message + org.springframework.boot spring-boot-autoconfigure diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java b/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java similarity index 89% rename from dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java rename to common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java index bcd560a6f9..a8f4545449 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/limits/DefaultRateLimitService.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.util.limits; +package org.thingsboard.server.cache.limits; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; @@ -28,10 +27,9 @@ import org.thingsboard.server.common.data.exception.TenantProfileNotFoundExcepti import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.limit.LimitedApi; -import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; +import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; import org.thingsboard.server.common.msg.tools.TbRateLimits; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import java.util.concurrent.TimeUnit; @@ -39,14 +37,14 @@ import java.util.concurrent.TimeUnit; @Slf4j public class DefaultRateLimitService implements RateLimitService { - private final TbTenantProfileCache tenantProfileCache; + private final TenantProfileProvider tenantProfileProvider; private final NotificationRuleProcessor notificationRuleProcessor; - public DefaultRateLimitService(TbTenantProfileCache tenantProfileCache, - @Lazy NotificationRuleProcessor notificationRuleProcessor, + public DefaultRateLimitService(TenantProfileProvider tenantProfileProvider, + NotificationRuleProcessor notificationRuleProcessor, @Value("${cache.rateLimits.timeToLiveInMinutes:120}") int rateLimitsTtl, @Value("${cache.rateLimits.maxSize:200000}") int rateLimitsCacheMaxSize) { - this.tenantProfileCache = tenantProfileCache; + this.tenantProfileProvider = tenantProfileProvider; this.notificationRuleProcessor = notificationRuleProcessor; this.rateLimits = Caffeine.newBuilder() .expireAfterAccess(rateLimitsTtl, TimeUnit.MINUTES) @@ -66,7 +64,7 @@ public class DefaultRateLimitService implements RateLimitService { if (tenantId.isSysTenantId()) { return true; } - TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + TenantProfile tenantProfile = tenantProfileProvider.get(tenantId); if (tenantProfile == null) { throw new TenantProfileNotFoundException(tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/limits/RateLimitService.java b/common/cache/src/main/java/org/thingsboard/server/cache/limits/RateLimitService.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/util/limits/RateLimitService.java rename to common/cache/src/main/java/org/thingsboard/server/cache/limits/RateLimitService.java index e24383bb62..84c22c514b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/limits/RateLimitService.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/limits/RateLimitService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.util.limits; +package org.thingsboard.server.cache.limits; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.limit.LimitedApi; diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/limits/TenantProfileProvider.java b/common/cache/src/main/java/org/thingsboard/server/cache/limits/TenantProfileProvider.java new file mode 100644 index 0000000000..15243d12c8 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/limits/TenantProfileProvider.java @@ -0,0 +1,25 @@ +/** + * 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.cache.limits; + +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; + +public interface TenantProfileProvider { + + TenantProfile get(TenantId tenantId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java index 23d4245575..e569745504 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java @@ -27,7 +27,7 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import javax.annotation.PreDestroy; diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java index 822768c572..59fdcac935 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java @@ -27,7 +27,7 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import javax.annotation.PreDestroy; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java index 4610973370..a9d118befb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.tenant; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.limits.TenantProfileProvider; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; @@ -31,7 +32,7 @@ import java.util.function.Consumer; @Service @Slf4j -public class DefaultTbTenantProfileCache implements TbTenantProfileCache { +public class DefaultTbTenantProfileCache implements TbTenantProfileCache, TenantProfileProvider { private final Lock tenantProfileFetchLock = new ReentrantLock(); private final TenantProfileService tenantProfileService; diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java index ebe2d59dcf..8474af9584 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.common.data.limit.LimitedApi; -import org.thingsboard.server.dao.util.limits.RateLimitService; +import org.thingsboard.server.cache.limits.RateLimitService; import javax.annotation.Nullable; import java.util.HashMap; From 3c460d79c3e6a468131c35b6738a597cfbb538df Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 26 Jan 2024 13:37:08 +0200 Subject: [PATCH 057/137] Refactor core consumer tests: add test for device state message forwarding, split all-in-one tests into more but simpler test cases --- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbCoreConsumerServiceTest.java | 350 +++++++++++++++--- 2 files changed, 290 insertions(+), 62 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 41f7cc012c..5c93351d0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -652,7 +652,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Fri, 26 Jan 2024 15:21:27 +0200 Subject: [PATCH 058/137] Fix DefaultRateLimitService init --- .../server/cache/limits/DefaultRateLimitService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java b/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java index a8f4545449..f3530e99d2 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/limits/DefaultRateLimitService.java @@ -20,6 +20,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.msg.tools.TbRateLimits; import java.util.concurrent.TimeUnit; +@Lazy @Service @Slf4j public class DefaultRateLimitService implements RateLimitService { @@ -41,7 +43,7 @@ public class DefaultRateLimitService implements RateLimitService { private final NotificationRuleProcessor notificationRuleProcessor; public DefaultRateLimitService(TenantProfileProvider tenantProfileProvider, - NotificationRuleProcessor notificationRuleProcessor, + @Lazy NotificationRuleProcessor notificationRuleProcessor, @Value("${cache.rateLimits.timeToLiveInMinutes:120}") int rateLimitsTtl, @Value("${cache.rateLimits.maxSize:200000}") int rateLimitsCacheMaxSize) { this.tenantProfileProvider = tenantProfileProvider; From eadcf916f5c57b938776a971797f6c0d416040c8 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 26 Jan 2024 16:15:11 +0200 Subject: [PATCH 059/137] Add optimization using separate service which routes activity actions to device state service directly, when running in monolith. Refactor rule node tests --- .../server/actors/ActorSystemContext.java | 5 + .../actors/ruleChain/DefaultTbContext.java | 6 + ...ClusteredRuleEngineDeviceStateManager.java | 110 +++++++++++++ .../LocalRuleEngineDeviceStateManager.java | 85 ++++++++++ ...teredRuleEngineDeviceStateManagerTest.java | 150 ++++++++++++++++++ ...LocalRuleEngineDeviceStateManagerTest.java | 131 +++++++++++++++ .../thingsboard/server/common/msg/TbMsg.java | 2 +- .../api/RuleEngineDeviceStateManager.java | 32 ++++ .../rule/engine/api/TbContext.java | 2 + .../rule/engine/action/TbDeviceStateNode.java | 106 +++---------- .../engine/action/TbDeviceStateNodeTest.java | 108 ++++++------- 11 files changed, 592 insertions(+), 145 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java create mode 100644 application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java create mode 100644 application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceStateManager.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 31d9e477ac..6082c3f9f0 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -31,6 +31,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationCenter; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; @@ -203,6 +204,10 @@ public class ActorSystemContext { @Getter private DeviceCredentialsService deviceCredentialsService; + @Autowired + @Getter + private RuleEngineDeviceStateManager deviceStateManager; + @Autowired @Getter private TbTenantProfileCache tenantProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 1e7d5d24e9..b97201005c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -683,6 +684,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getDeviceCredentialsService(); } + @Override + public RuleEngineDeviceStateManager getDeviceStateManager() { + return mainCtx.getDeviceStateManager(); + } + @Override public TbClusterService getClusterService() { return mainCtx.getClusterService(); diff --git a/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java new file mode 100644 index 0000000000..2a73d3f544 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java @@ -0,0 +1,110 @@ +/** + * 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.state; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.SimpleTbQueueCallback; +import org.thingsboard.server.queue.util.TbRuleEngineComponent; + +@Slf4j +@Service +@TbRuleEngineComponent +@RequiredArgsConstructor +public class ClusteredRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { + + private final TbClusterService clusterService; + + @Override + public void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback) { + var tenantUuid = tenantId.getId(); + var deviceUuid = deviceId.getId(); + var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastConnectTime(connectTime) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(deviceConnectMsg) + .build(); + log.trace("[{}][{}] Sending device connect message to core. Connect time: [{}].", tenantUuid, deviceUuid, connectTime); + clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); + } + + @Override + public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback) { + var tenantUuid = tenantId.getId(); + var deviceUuid = deviceId.getId(); + var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastActivityTime(activityTime) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(deviceActivityMsg) + .build(); + log.trace("[{}][{}] Sending device activity message to core. Activity time: [{}].", tenantUuid, deviceUuid, activityTime); + clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); + } + + @Override + public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback) { + var tenantUuid = tenantId.getId(); + var deviceUuid = deviceId.getId(); + var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastDisconnectTime(disconnectTime) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(deviceDisconnectMsg) + .build(); + log.trace("[{}][{}] Sending device disconnect message to core. Disconnect time: [{}].", tenantUuid, deviceUuid, disconnectTime); + clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); + } + + @Override + public void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback) { + var tenantUuid = tenantId.getId(); + var deviceUuid = deviceId.getId(); + var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(tenantUuid.getMostSignificantBits()) + .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) + .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) + .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) + .setLastInactivityTime(inactivityTime) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(deviceInactivityMsg) + .build(); + log.trace("[{}][{}] Sending device inactivity message to core. Inactivity time: [{}].", tenantUuid, deviceUuid, inactivityTime); + clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java new file mode 100644 index 0000000000..180aea9ecb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java @@ -0,0 +1,85 @@ +/** + * 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.state; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Slf4j +@Service +@Primary +@TbCoreComponent +@RequiredArgsConstructor +public class LocalRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { + + private final DeviceStateService deviceStateService; + + @Override + public void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback) { + try { + deviceStateService.onDeviceConnect(tenantId, deviceId, connectTime); + } catch (Exception e) { + log.error("[{}][{}] Failed to process device connect event. Connect time: [{}].", tenantId.getId(), deviceId.getId(), connectTime, e); + callback.onFailure(e); + return; + } + callback.onSuccess(); + } + + @Override + public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback) { + try { + deviceStateService.onDeviceActivity(tenantId, deviceId, activityTime); + } catch (Exception e) { + log.error("[{}][{}] Failed to process device activity event. Activity time: [{}].", tenantId.getId(), deviceId.getId(), activityTime, e); + callback.onFailure(e); + return; + } + callback.onSuccess(); + } + + @Override + public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback) { + try { + deviceStateService.onDeviceDisconnect(tenantId, deviceId, disconnectTime); + } catch (Exception e) { + log.error("[{}][{}] Failed to process device disconnect event. Disconnect time: [{}].", tenantId.getId(), deviceId.getId(), disconnectTime, e); + callback.onFailure(e); + return; + } + callback.onSuccess(); + } + + @Override + public void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback) { + try { + deviceStateService.onDeviceInactivity(tenantId, deviceId, inactivityTime); + } catch (Exception e) { + log.error("[{}][{}] Failed to process device inactivity event. Inactivity time: [{}].", tenantId.getId(), deviceId.getId(), inactivityTime, e); + callback.onFailure(e); + return; + } + callback.onSuccess(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java b/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java new file mode 100644 index 0000000000..e972b49347 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java @@ -0,0 +1,150 @@ +/** + * 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.state; + +import org.junit.jupiter.api.BeforeEach; +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.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; + +import java.util.UUID; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; + +@ExtendWith(MockitoExtension.class) +public class ClusteredRuleEngineDeviceStateManagerTest { + + @Mock + private static TbClusterService tbClusterServiceMock; + @Mock + private static TbCallback tbCallbackMock; + @Mock + private static TbQueueMsgMetadata metadataMock; + @Captor + private static ArgumentCaptor queueCallbackCaptor; + private static ClusteredRuleEngineDeviceStateManager deviceStateManager; + + private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002")); + private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002"); + private static final long EVENT_TS = System.currentTimeMillis(); + + @BeforeEach + public void setup() { + deviceStateManager = new ClusteredRuleEngineDeviceStateManager(tbClusterServiceMock); + } + + @ParameterizedTest + @MethodSource + public void givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) { + // WHEN + onDeviceAction.run(); + + // THEN + actionVerification.run(); + + TbQueueCallback callback = queueCallbackCaptor.getValue(); + callback.onSuccess(metadataMock); + then(tbCallbackMock).should().onSuccess(); + + var runtimeException = new RuntimeException("Something bad happened!"); + callback.onFailure(runtimeException); + then(tbCallbackMock).should().onFailure(runtimeException); + } + + private static Stream givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastConnectTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(deviceConnectMsg) + .build(); + then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastActivityTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(deviceActivityMsg) + .build(); + then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastDisconnectTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(deviceDisconnectMsg) + .build(); + then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastInactivityTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(deviceInactivityMsg) + .build(); + then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ) + ); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java b/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java new file mode 100644 index 0000000000..db4d71f995 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java @@ -0,0 +1,131 @@ +/** + * 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.state; + +import org.junit.jupiter.api.BeforeEach; +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.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.TbCallback; + +import java.util.UUID; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +public class LocalRuleEngineDeviceStateManagerTest { + + @Mock + private static DeviceStateService deviceStateServiceMock; + @Mock + private static TbCallback tbCallbackMock; + private static LocalRuleEngineDeviceStateManager deviceStateManager; + + private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002")); + private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002"); + private static final long EVENT_TS = System.currentTimeMillis(); + private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException("Something bad happened!"); + + @BeforeEach + public void setup() { + deviceStateManager = new LocalRuleEngineDeviceStateManager(deviceStateServiceMock); + } + + @ParameterizedTest + @MethodSource + public void givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) { + // WHEN + onDeviceAction.run(); + + // THEN + actionVerification.run(); + then(tbCallbackMock).should().onSuccess(); + then(tbCallbackMock).should(never()).onFailure(any()); + } + + private static Stream givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ) + ); + } + + @ParameterizedTest + @MethodSource + public void givenProcessingFailure_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnFailureCallback( + Runnable exceptionThrowSetup, Runnable onDeviceAction, Runnable actionVerification + ) { + // GIVEN + exceptionThrowSetup.run(); + + // WHEN + onDeviceAction.run(); + + // THEN + actionVerification.run(); + then(tbCallbackMock).should(never()).onSuccess(); + then(tbCallbackMock).should().onFailure(RUNTIME_EXCEPTION); + } + + private static Stream givenProcessingFailure_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnFailureCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ) + ); + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 1e06d75b15..d81393cf25 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -335,7 +335,7 @@ public final class TbMsg implements Serializable { this.originator = originator; if (customerId == null || customerId.isNullUid()) { if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { - this.customerId = (CustomerId) originator; + this.customerId = new CustomerId(originator.getId()); } else { this.customerId = null; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceStateManager.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceStateManager.java new file mode 100644 index 0000000000..82c2916408 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceStateManager.java @@ -0,0 +1,32 @@ +/** + * 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.api; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.TbCallback; + +public interface RuleEngineDeviceStateManager { + + void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback); + + void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback); + + void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback); + + void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 59f5b128a7..32fb81ccd9 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -280,6 +280,8 @@ public interface TbContext { DeviceCredentialsService getDeviceCredentialsService(); + RuleEngineDeviceStateManager getDeviceStateManager(); + TbClusterService getClusterService(); DashboardService getDashboardService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index f28a3e4467..7168ad3c0f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.action; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -23,12 +24,12 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.queue.common.SimpleTbQueueCallback; +import org.thingsboard.server.common.msg.queue.TbCallback; import java.util.EnumSet; import java.util.Set; @@ -61,7 +62,7 @@ import java.util.Set; ) public class TbDeviceStateNode implements TbNode { - static final Set SUPPORTED_EVENTS = EnumSet.of( + private static final Set SUPPORTED_EVENTS = EnumSet.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); @@ -85,98 +86,41 @@ public class TbDeviceStateNode implements TbNode { ctx.tellSuccess(msg); return; } + + TenantId tenantId = ctx.getTenantId(); + DeviceId deviceId = (DeviceId) msg.getOriginator(); + RuleEngineDeviceStateManager deviceStateManager = ctx.getDeviceStateManager(); + switch (event) { case CONNECT_EVENT: - sendDeviceConnectMsg(ctx, msg); + deviceStateManager.onDeviceConnect(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); break; case ACTIVITY_EVENT: - sendDeviceActivityMsg(ctx, msg); + deviceStateManager.onDeviceActivity(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); break; case DISCONNECT_EVENT: - sendDeviceDisconnectMsg(ctx, msg); + deviceStateManager.onDeviceDisconnect(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); break; case INACTIVITY_EVENT: - sendDeviceInactivityMsg(ctx, msg); + deviceStateManager.onDeviceInactivity(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); break; default: ctx.tellFailure(msg, new IllegalStateException("Configured event [" + event + "] is not supported!")); } } - private void sendDeviceConnectMsg(TbContext ctx, TbMsg msg) { - var tenantUuid = ctx.getTenantId().getId(); - var deviceUuid = msg.getOriginator().getId(); - var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastConnectTime(msg.getMetaDataTs()) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceConnectMsg(deviceConnectMsg) - .build(); - ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) - ); - } - - private void sendDeviceActivityMsg(TbContext ctx, TbMsg msg) { - var tenantUuid = ctx.getTenantId().getId(); - var deviceUuid = msg.getOriginator().getId(); - var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastActivityTime(msg.getMetaDataTs()) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceActivityMsg(deviceActivityMsg) - .build(); - ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) - ); - } - - private void sendDeviceDisconnectMsg(TbContext ctx, TbMsg msg) { - var tenantUuid = ctx.getTenantId().getId(); - var deviceUuid = msg.getOriginator().getId(); - var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastDisconnectTime(msg.getMetaDataTs()) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceDisconnectMsg(deviceDisconnectMsg) - .build(); - ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) - ); - } - - private void sendDeviceInactivityMsg(TbContext ctx, TbMsg msg) { - var tenantUuid = ctx.getTenantId().getId(); - var deviceUuid = msg.getOriginator().getId(); - var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastInactivityTime(msg.getMetaDataTs()) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceInactivityMsg(deviceInactivityMsg) - .build(); - ctx.getClusterService().pushMsgToCore( - ctx.getTenantId(), msg.getOriginator(), toCoreMsg, getMsgEnqueuedCallback(ctx, msg) - ); - } + private TbCallback getMsgEnqueuedCallback(TbContext ctx, TbMsg msg) { + return new TbCallback() { + @Override + public void onSuccess() { + ctx.tellSuccess(msg); + } - private TbQueueCallback getMsgEnqueuedCallback(TbContext ctx, TbMsg msg) { - return new SimpleTbQueueCallback(metadata -> ctx.tellSuccess(msg), t -> ctx.tellFailure(msg, t)); + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }; } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 988d8911a4..e967ec49b5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -20,28 +20,28 @@ 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.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.common.msg.queue.TbCallback; -import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -51,7 +51,6 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.times; @ExtendWith(MockitoExtension.class) public class TbDeviceStateNodeTest { @@ -59,7 +58,9 @@ public class TbDeviceStateNodeTest { @Mock private TbContext ctxMock; @Mock - private TbClusterService tbClusterServiceMock; + private static RuleEngineDeviceStateManager deviceStateManagerMock; + @Captor + private static ArgumentCaptor callbackCaptor; private TbDeviceStateNode node; private TbDeviceStateNodeConfiguration config; @@ -94,10 +95,6 @@ public class TbDeviceStateNodeTest { @Test public void givenDefaultConfiguration_whenInvoked_thenCorrectValuesAreSet() { assertThat(config.getEvent()).isEqualTo(TbMsgType.ACTIVITY_EVENT); - assertThat(TbDeviceStateNode.SUPPORTED_EVENTS).isEqualTo(Set.of( - TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, - TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT - )); } @Test @@ -113,10 +110,14 @@ public class TbDeviceStateNodeTest { .matches(e -> ((TbNodeException) e).isUnrecoverable()); } - @Test - public void givenUnsupportedEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException() { + @ParameterizedTest + @EnumSource( + value = TbMsgType.class, + names = {"CONNECT_EVENT", "ACTIVITY_EVENT", "DISCONNECT_EVENT", "INACTIVITY_EVENT"}, + mode = EnumSource.Mode.EXCLUDE + ) + public void givenUnsupportedEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException(TbMsgType unsupportedEvent) { // GIVEN - var unsupportedEvent = TbMsgType.TO_SERVER_RPC_REQUEST; config.setEvent(unsupportedEvent); var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); @@ -127,11 +128,23 @@ public class TbDeviceStateNodeTest { .matches(e -> ((TbNodeException) e).isUnrecoverable()); } - @Test - public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered() { + @ParameterizedTest + @EnumSource(value = EntityType.class, names = "DEVICE", mode = EnumSource.Mode.EXCLUDE) + public void givenNonDeviceOriginator_whenOnMsg_thenTellsSuccessAndNoActivityActionsTriggered(EntityType unsupportedType) { // GIVEN - var asset = new AssetId(UUID.randomUUID()); - var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, asset, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); + var nonDeviceOriginator = new EntityId() { + + @Override + public UUID getId() { + return UUID.randomUUID(); + } + + @Override + public EntityType getEntityType() { + return unsupportedType; + } + }; + var msg = TbMsg.newMsg(TbMsgType.ENTITY_CREATED, nonDeviceOriginator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT); // WHEN node.onMsg(ctxMock, msg); @@ -142,10 +155,10 @@ public class TbDeviceStateNodeTest { } @ParameterizedTest - @MethodSource("provideSupportedEventsAndExpectedMessages") - public void givenSupportedEvent_whenOnMsg_thenCorrectMsgIsSent(TbMsgType event, TransportProtos.ToCoreMsg expectedToCoreMsg) { + @MethodSource + public void givenInactivityEventAndDeviceOriginator_whenOnMsg_thenOnDeviceInactivityIsCalledWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { // GIVEN - config.setEvent(event); + config.setEvent(supportedEventType); var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); try { node.init(ctxMock, nodeConfig); @@ -153,65 +166,34 @@ public class TbDeviceStateNodeTest { fail("Node failed to initialize!", e); } given(ctxMock.getTenantId()).willReturn(TENANT_ID); - given(ctxMock.getClusterService()).willReturn(tbClusterServiceMock); + given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); // WHEN node.onMsg(ctxMock, msg); // THEN - var protoCaptor = ArgumentCaptor.forClass(TransportProtos.ToCoreMsg.class); - var callbackCaptor = ArgumentCaptor.forClass(TbQueueCallback.class); - then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), protoCaptor.capture(), callbackCaptor.capture()); + actionVerification.run(); - TbQueueCallback actualCallback = callbackCaptor.getValue(); + TbCallback actualCallback = callbackCaptor.getValue(); - actualCallback.onSuccess(null); + actualCallback.onSuccess(); then(ctxMock).should().tellSuccess(msg); var throwable = new Throwable(); actualCallback.onFailure(throwable); then(ctxMock).should().tellFailure(msg, throwable); - assertThat(expectedToCoreMsg).isEqualTo(protoCaptor.getValue()); - then(tbClusterServiceMock).shouldHaveNoMoreInteractions(); + then(deviceStateManagerMock).shouldHaveNoMoreInteractions(); then(ctxMock).shouldHaveNoMoreInteractions(); } - private static Stream provideSupportedEventsAndExpectedMessages() { + private static Stream givenInactivityEventAndDeviceOriginator_whenOnMsg_thenOnDeviceInactivityIsCalledWithCorrectCallback() { return Stream.of( - Arguments.of(TbMsgType.CONNECT_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceConnectMsg( - TransportProtos.DeviceConnectProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastConnectTime(METADATA_TS) - .build()).build()), - Arguments.of(TbMsgType.ACTIVITY_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg( - TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastActivityTime(METADATA_TS) - .build()).build()), - Arguments.of(TbMsgType.DISCONNECT_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceDisconnectMsg( - TransportProtos.DeviceDisconnectProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastDisconnectTime(METADATA_TS) - .build()).build()), - Arguments.of(TbMsgType.INACTIVITY_EVENT, TransportProtos.ToCoreMsg.newBuilder().setDeviceInactivityMsg( - TransportProtos.DeviceInactivityProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastInactivityTime(METADATA_TS) - .build()).build()) + Arguments.of(TbMsgType.CONNECT_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceConnect(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())), + Arguments.of(TbMsgType.ACTIVITY_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceActivity(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())), + Arguments.of(TbMsgType.DISCONNECT_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceDisconnect(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())), + Arguments.of(TbMsgType.INACTIVITY_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceInactivity(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())) ); } From b1b0e44c7fda9118cea95b6715430cf1641943bc Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 26 Jan 2024 17:04:49 +0200 Subject: [PATCH 060/137] Add executor to process activity events in a dedicated thread not to avoid blocking main consumer --- .../queue/DefaultTbCoreConsumerService.java | 64 +++++++++++-------- .../DefaultTbCoreConsumerServiceTest.java | 23 +++++++ 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 5c93351d0a..9942476c01 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.service.queue; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -148,6 +151,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService future = deviceActivityEventsExecutor.submit(() -> stateService.onDeviceConnect(tenantId, deviceId, deviceConnectMsg.getLastConnectTime())); + DonAsynchron.withCallback(future, + __ -> callback.onSuccess(), + t -> { + log.warn("[{}] Failed to process device connect message for device [{}]", tenantId.getId(), deviceId.getId(), t); + callback.onFailure(t); + }); } void forwardToStateService(TransportProtos.DeviceActivityProto deviceActivityMsg, TbCallback callback) { @@ -680,13 +688,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService future = deviceActivityEventsExecutor.submit(() -> stateService.onDeviceActivity(tenantId, deviceId, deviceActivityMsg.getLastActivityTime())); + DonAsynchron.withCallback(future, + __ -> callback.onSuccess(), + t -> { + log.warn("[{}] Failed to process device activity message for device [{}]", tenantId.getId(), deviceId.getId(), t); + callback.onFailure(new RuntimeException("Failed to update device activity for device [" + deviceId.getId() + "]!", t)); + }); } void forwardToStateService(TransportProtos.DeviceDisconnectProto deviceDisconnectMsg, TbCallback callback) { @@ -695,13 +703,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService future = deviceActivityEventsExecutor.submit(() -> stateService.onDeviceDisconnect(tenantId, deviceId, deviceDisconnectMsg.getLastDisconnectTime())); + DonAsynchron.withCallback(future, + __ -> callback.onSuccess(), + t -> { + log.warn("[{}] Failed to process device disconnect message for device [{}]", tenantId.getId(), deviceId.getId(), t); + callback.onFailure(t); + }); } void forwardToStateService(TransportProtos.DeviceInactivityProto deviceInactivityMsg, TbCallback callback) { @@ -710,13 +718,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService future = deviceActivityEventsExecutor.submit(() -> stateService.onDeviceInactivity(tenantId, deviceId, deviceInactivityMsg.getLastInactivityTime())); + DonAsynchron.withCallback(future, + __ -> callback.onSuccess(), + t -> { + log.warn("[{}] Failed to process device inactivity message for device [{}]", tenantId.getId(), deviceId.getId(), t); + callback.onFailure(t); + }); } private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) { diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java index 19166f75d2..621d4c5a91 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerServiceTest.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.service.queue; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,6 +32,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.service.state.DeviceStateService; import java.util.UUID; +import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -52,12 +56,31 @@ public class DefaultTbCoreConsumerServiceTest { private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); private final long time = System.currentTimeMillis(); + private ListeningExecutorService executor; + @Mock private DefaultTbCoreConsumerService defaultTbCoreConsumerServiceMock; @BeforeEach public void setup() { + executor = MoreExecutors.newDirectExecutorService(); ReflectionTestUtils.setField(defaultTbCoreConsumerServiceMock, "stateService", stateServiceMock); + ReflectionTestUtils.setField(defaultTbCoreConsumerServiceMock, "deviceActivityEventsExecutor", executor); + } + + @AfterEach + public void cleanup() { + if (executor != null) { + executor.shutdown(); + try { + if (!executor.awaitTermination(10L, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } } @Test From b19d7f1406775e0afd511e46ecd08a4594907064 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 26 Jan 2024 17:08:50 +0200 Subject: [PATCH 061/137] Clarify information about event ts in node description --- .../rule/engine/action/TbDeviceStateNode.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 7168ad3c0f..d8cc81679c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -39,12 +39,9 @@ import java.util.Set; type = ComponentType.ACTION, name = "device state", nodeDescription = "Triggers device connectivity events", - nodeDetails = "If incoming message originator is a device," + - " registers configured event for that device in the Device State Service," + - " which sends appropriate message to the Rule Engine." + - " If metadata ts property is present, it will be used as event timestamp." + - " Incoming message is forwarded using the Success chain," + - " unless an unexpected error occurs during message processing" + + nodeDetails = "If incoming message originator is a device, registers configured event for that device in the Device State Service, which sends appropriate message to the Rule Engine." + + " If metadata ts property is present, it will be used as event timestamp. Otherwise, the message timestamp will be used." + + " Incoming message is forwarded using the Success chain, unless an unexpected error occurs during message processing" + " then incoming message is forwarded using the Failure chain." + "
" + "Supported device connectivity events are:" + @@ -54,8 +51,7 @@ import java.util.Set; "
  • Activity event
  • " + "
  • Inactivity event
  • " + "" + - "This node is particularly useful when device isn't using transports to receive data," + - " such as when fetching data from external API or computing new data within the rule chain.", + "This node is particularly useful when device isn't using transports to receive data, such as when fetching data from external API or computing new data within the rule chain.", configClazz = TbDeviceStateNodeConfiguration.class, uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeDeviceStateConfig" From 89713558cf62351b13226a0d8dbf4412a5372735 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Sun, 28 Jan 2024 21:02:38 +0100 Subject: [PATCH 062/137] MqttClient use Promise extends Future to have full async capabilities provided by Netty (addListener, etc) --- .../src/main/java/org/thingsboard/mqtt/MqttClient.java | 7 ++++--- .../main/java/org/thingsboard/mqtt/MqttClientImpl.java | 8 ++++---- .../thingsboard/mqtt/integration/MqttIntegrationTest.java | 3 ++- .../java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java index 536a76119f..93bcee181c 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java @@ -21,6 +21,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; import org.thingsboard.common.util.ListeningExecutor; public interface MqttClient { @@ -32,7 +33,7 @@ public interface MqttClient { * @param host The ip address or host to connect to * @return A future which will be completed when the connection is opened and we received an CONNACK */ - Future connect(String host); + Promise connect(String host); /** * Connect to the specified hostname/ip using the specified port @@ -41,7 +42,7 @@ public interface MqttClient { * @param port The tcp port to connect to * @return A future which will be completed when the connection is opened and we received an CONNACK */ - Future connect(String host, int port); + Promise connect(String host, int port); /** * @@ -55,7 +56,7 @@ public interface MqttClient { * @return A future which will be completed when the connection is opened and we received an CONNACK * @throws IllegalStateException if no previous {@link #connect(String, int)} calls were attempted */ - Future reconnect(); + Promise reconnect(); /** * Retrieve the netty {@link EventLoopGroup} we are using diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index 63d65a1cc2..cf8287e904 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -118,7 +118,7 @@ final class MqttClientImpl implements MqttClient { * @return A future which will be completed when the connection is opened and we received an CONNACK */ @Override - public Future connect(String host) { + public Promise connect(String host) { return connect(host, 1883); } @@ -130,11 +130,11 @@ final class MqttClientImpl implements MqttClient { * @return A future which will be completed when the connection is opened and we received an CONNACK */ @Override - public Future connect(String host, int port) { + public Promise connect(String host, int port) { return connect(host, port, false); } - private Future connect(String host, int port, boolean reconnect) { + private Promise connect(String host, int port, boolean reconnect) { log.trace("[{}] Connecting to server, isReconnect - {}", channel != null ? channel.id() : "UNKNOWN", reconnect); if (this.eventLoop == null) { this.eventLoop = new NioEventLoopGroup(); @@ -199,7 +199,7 @@ final class MqttClientImpl implements MqttClient { } @Override - public Future reconnect() { + public Promise reconnect() { log.trace("[{}] Reconnecting to server, isReconnect - {}", channel != null ? channel.id() : "UNKNOWN", reconnect); if (host == null) { throw new IllegalStateException("Cannot reconnect. Call connect() first"); diff --git a/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java b/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java index 04c2be1740..1f6f9c85a6 100644 --- a/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java +++ b/netty-mqtt/src/test/java/org/thingsboard/mqtt/integration/MqttIntegrationTest.java @@ -21,6 +21,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; @@ -127,7 +128,7 @@ public class MqttIntegrationTest { config.setReconnectDelay(RECONNECT_DELAY_SECONDS); MqttClient client = MqttClient.create(config, null, handlerExecutor); client.setEventLoop(this.eventLoopGroup); - Future connectFuture = client.connect(MQTT_HOST, this.mqttServer.getMqttPort()); + Promise connectFuture = client.connect(MQTT_HOST, this.mqttServer.getMqttPort()); String hostPort = MQTT_HOST + ":" + this.mqttServer.getMqttPort(); MqttConnectResult result; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index b9ee65675a..159c6a450e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -18,7 +18,7 @@ package org.thingsboard.rule.engine.mqtt; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.ssl.SslContext; -import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; import lombok.extern.slf4j.Slf4j; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; @@ -121,7 +121,7 @@ public class TbMqttNode extends TbAbstractExternalNode { prepareMqttClientConfig(config); MqttClient client = MqttClient.create(config, null, ctx.getExternalCallExecutor()); client.setEventLoop(ctx.getSharedEventLoop()); - Future connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort()); + Promise connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort()); MqttConnectResult result; try { result = connectFuture.get(this.mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS); From b8be8b3b5ee02127cc287ff32a37e796f572eff7 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Mon, 29 Jan 2024 11:41:29 +0200 Subject: [PATCH 063/137] Added URL Suffix generation function which is imported and used to generate the correct path --- .../data/json/tenant/dashboards/gateways.json | 36 +++++++++---------- ui-ngx/src/app/core/api/widget-api.models.ts | 1 + .../home/models/widget-component.models.ts | 4 ++- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/application/src/main/data/json/tenant/dashboards/gateways.json b/application/src/main/data/json/tenant/dashboards/gateways.json index 0ddfc98705..4ccee86d5e 100644 --- a/application/src/main/data/json/tenant/dashboards/gateways.json +++ b/application/src/main/data/json/tenant/dashboards/gateways.json @@ -2317,7 +2317,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "45e4507d-3adc-bb31-8b2b-1ba09bbd56ac" @@ -2484,7 +2484,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "852eccce-98eb-24db-c783-bdd62566f906" @@ -2650,7 +2650,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "3c31ba62-e760-2bea-4c8d-d32784a86c24" @@ -2816,7 +2816,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "4b55ea81-93bf-4206-9166-3e0bdc1dd9f3" @@ -2982,7 +2982,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "babf88d0-a118-e2b5-f10e-3a5970c8a65b" @@ -3148,7 +3148,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "94de7690-f91d-b032-6771-85af99abd749" @@ -3314,7 +3314,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "18414f44-1c65-536a-14de-eaf21a7d56bd" @@ -3480,7 +3480,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "794974da-c9d2-a9f7-be47-c9eb642094e8" @@ -3646,7 +3646,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "2add705b-3e53-8559-8126-380cac686fb0" @@ -3812,7 +3812,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "7e1ba820-9992-d52a-579b-20485abb3926" @@ -3978,7 +3978,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "91af27c1-b37c-2276-6022-a332e41b2b33" @@ -4144,7 +4144,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "26cf8696-054b-13ec-7984-6fc5df20e6f1" @@ -4310,7 +4310,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "1dcfaf24-32be-cd19-62d6-86d12cc6a7ef" @@ -4476,7 +4476,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "ad2bc817-f3c4-150c-4672-8fe0c38aee8d" @@ -4642,7 +4642,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "d1ad84cd-bd9c-4dca-e4a0-f444ae8598bd" @@ -4808,7 +4808,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "bf80eef9-b879-9a08-40a4-488dbdefa125" @@ -4974,7 +4974,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "b5a406b3-cc0a-8a09-9aec-3f8befae5fb8" @@ -5140,7 +5140,7 @@ "useShowWidgetActionFunction": null, "showWidgetActionFunction": "return true;", "type": "custom", - "customFunction": "const url = `${window.location.origin}/entities/devices/${entityId.id}`;\nwindow.open(url, '_blank');", + "customFunction": "const url = `${window.location.origin + widgetContext.utils.getEntityDetailsPageURL(entityId.id, entityId.entityType)}`;\nwindow.open(url, '_blank');", "openInSeparateDialog": false, "openInPopover": false, "id": "ec1dfba3-4b43-2491-8948-f602337f8a3b" diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 548cbc7041..75198b5a93 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -82,6 +82,7 @@ export interface RpcApi { export interface IWidgetUtils { formatValue: (value: any, dec?: number, units?: string, showZeroDecimals?: boolean) => string | undefined; + getEntityDetailsPageURL: (id: string, entityType: EntityType) => string; } export interface WidgetActionsApi { diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 4a7fc32502..213f10290b 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -57,6 +57,7 @@ import { createLabelFromDatasource, createLabelFromSubscriptionEntityInfo, formatValue, + getEntityDetailsPageURL, hasDatasourceLabelsVariables, isDefined } from '@core/utils'; @@ -253,7 +254,8 @@ export class WidgetContext { }; utils: IWidgetUtils = { - formatValue + formatValue, + getEntityDetailsPageURL }; $widgetElement: JQuery; From 582259afd45131a1e85114ce249c43e220b0cb3a Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Mon, 29 Jan 2024 12:43:51 +0200 Subject: [PATCH 064/137] UI: timewindow scss improvement --- .../app/shared/components/time/timewindow.component.scss | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.scss b/ui-ngx/src/app/shared/components/time/timewindow.component.scss index d793bc5367..99f3d8367d 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -58,12 +58,7 @@ .mdc-button__label { overflow: hidden; text-overflow: ellipsis; - height: 100%; - - span { - display: block; - height: 100%; - } + white-space: nowrap; } } } From 13c7b5e868b81d492ba1e63fd84c6e907e144253 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Mon, 29 Jan 2024 15:26:41 +0200 Subject: [PATCH 065/137] Removed extra > character --- .../app/shared/components/dashboard-autocomplete.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html index 5d41da6b4a..ab3694773e 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html @@ -65,7 +65,7 @@ - > +
    From d509c230c0563e0d84cdcfa7f8f2cb0f0d890c48 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Tue, 30 Jan 2024 10:35:21 +0200 Subject: [PATCH 066/137] Replace hardcoded doc-link path with site-base-url --- .../help/en_US/rulenode/common_node_fields_templatization.md | 2 +- .../src/assets/help/en_US/rulenode/common_node_script_args.md | 2 +- ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md | 2 +- .../assets/help/en_US/rulenode/tbel/common_node_script_args.md | 2 +- .../assets/help/en_US/rulenode/tbel/switch_node_script_fn.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/assets/help/en_US/rulenode/common_node_fields_templatization.md b/ui-ngx/src/assets/help/en_US/rulenode/common_node_fields_templatization.md index 0ccbbd9058..50301746c4 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/common_node_fields_templatization.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/common_node_fields_templatization.md @@ -1,4 +1,4 @@ Fields templatization feature allows you to process the incoming messages with dynamic configuration by substitution of templates specified in the configuration fields with values from message or message metadata. -For more detailed information, please refer to the ThingsBoard [documentation](https://thingsboard.io/docs/user-guide/templatization/) +For more detailed information, please refer to the ThingsBoard [documentation](${siteBaseUrl}/docs/user-guide/templatization/) diff --git a/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md b/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md index 296e5b735c..d9b5bdb0b7 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md @@ -8,4 +8,4 @@ Enable 'debug mode' for your rule node to see the messages that arrive in near real-time. -See Debugging for more information. \ No newline at end of file +See Debugging for more information. diff --git a/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md index a31eae72cf..0f6ab96736 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md @@ -15,7 +15,7 @@ JavaScript function computing **an array of Link names** to forward the incoming Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.
    If the result is an empty array - message will not be routed to any Node and will be immediately -acknowledged. +acknowledged.
    diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md index 296e5b735c..d9b5bdb0b7 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md @@ -8,4 +8,4 @@ Enable 'debug mode' for your rule node to see the messages that arrive in near real-time. -See Debugging for more information. \ No newline at end of file +See Debugging for more information. diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md index 7fd3b7447b..aed694848e 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md @@ -15,7 +15,7 @@ Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.
    If the result is an empty array - message will not be routed to any Node and will be immediately -acknowledged. +acknowledged.
    From d1a9c4432acb5398497a01b148e01018933fa0b3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 30 Jan 2024 11:56:10 +0200 Subject: [PATCH 067/137] Add rate limit of at most one event per second per originator in node --- .../server/common/data/msg/TbMsgType.java | 1 + .../rule/engine/action/TbDeviceStateNode.java | 86 +++++++- .../engine/action/TbDeviceStateNodeTest.java | 187 +++++++++++++++--- 3 files changed, 239 insertions(+), 35 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index 763f245a09..c7eeef158d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -76,6 +76,7 @@ public enum TbMsgType { DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), DELAY_TIMEOUT_SELF_MSG(null, true), MSG_COUNT_SELF_MSG(null, true), + DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG(null, true), // Custom or N/A type: NA; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index d8cc81679c..a74df13f20 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.action; +import com.google.common.base.Stopwatch; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.RuleNode; @@ -29,10 +30,15 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.TbCallback; +import java.time.Duration; import java.util.EnumSet; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; @Slf4j @RuleNode( @@ -61,7 +67,11 @@ public class TbDeviceStateNode implements TbNode { private static final Set SUPPORTED_EVENTS = EnumSet.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); + private static final Duration ONE_SECOND = Duration.ofSeconds(1L); + private static final Duration ENTRY_EXPIRATION_TIME = Duration.ofDays(1L); + private Stopwatch stopwatch; + private ConcurrentMap lastActivityEventTimestamps; private TbMsgType event; @Override @@ -74,31 +84,82 @@ public class TbDeviceStateNode implements TbNode { throw new TbNodeException("Unsupported event: " + event, true); } this.event = event; + lastActivityEventTimestamps = new ConcurrentHashMap<>(); + stopwatch = Stopwatch.createStarted(); + scheduleCleanupMsg(ctx); } @Override public void onMsg(TbContext ctx, TbMsg msg) { + if (msg.isTypeOf(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG)) { + removeStaleEntries(); + scheduleCleanupMsg(ctx); + ctx.ack(msg); + return; + } + if (!EntityType.DEVICE.equals(msg.getOriginator().getEntityType())) { ctx.tellSuccess(msg); return; } + DeviceId originator = new DeviceId(msg.getOriginator().getId()); + + lastActivityEventTimestamps.compute(originator, (__, lastEventTs) -> { + Duration now = stopwatch.elapsed(); + + if (lastEventTs == null) { + sendEvent(ctx, originator, msg); + return now; + } + + Duration elapsedSinceLastEventSent = now.minus(lastEventTs); + if (elapsedSinceLastEventSent.compareTo(ONE_SECOND) < 0) { + ctx.tellSuccess(msg); + return lastEventTs; + } + + sendEvent(ctx, originator, msg); + + return now; + }); + } + + private void scheduleCleanupMsg(TbContext ctx) { + TbMsg cleanupMsg = ctx.newMsg( + null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING + ); + ctx.tellSelf(cleanupMsg, Duration.ofHours(1L).toMillis()); + } + + private void removeStaleEntries() { + lastActivityEventTimestamps.entrySet().removeIf(entry -> { + Duration now = stopwatch.elapsed(); + Duration lastEventTs = entry.getValue(); + Duration elapsedSinceLastEventSent = now.minus(lastEventTs); + return elapsedSinceLastEventSent.compareTo(ENTRY_EXPIRATION_TIME) > 0; + }); + } + + private void sendEvent(TbContext ctx, DeviceId originator, TbMsg msg) { TenantId tenantId = ctx.getTenantId(); - DeviceId deviceId = (DeviceId) msg.getOriginator(); + long eventTs = msg.getMetaDataTs(); + RuleEngineDeviceStateManager deviceStateManager = ctx.getDeviceStateManager(); + TbCallback callback = getMsgEnqueuedCallback(ctx, msg); switch (event) { case CONNECT_EVENT: - deviceStateManager.onDeviceConnect(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); + deviceStateManager.onDeviceConnect(tenantId, originator, eventTs, callback); break; case ACTIVITY_EVENT: - deviceStateManager.onDeviceActivity(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); + deviceStateManager.onDeviceActivity(tenantId, originator, eventTs, callback); break; case DISCONNECT_EVENT: - deviceStateManager.onDeviceDisconnect(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); + deviceStateManager.onDeviceDisconnect(tenantId, originator, eventTs, callback); break; case INACTIVITY_EVENT: - deviceStateManager.onDeviceInactivity(tenantId, deviceId, msg.getMetaDataTs(), getMsgEnqueuedCallback(ctx, msg)); + deviceStateManager.onDeviceInactivity(tenantId, originator, eventTs, callback); break; default: ctx.tellFailure(msg, new IllegalStateException("Configured event [" + event + "] is not supported!")); @@ -119,4 +180,19 @@ public class TbDeviceStateNode implements TbNode { }; } + @Override + public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { + lastActivityEventTimestamps.entrySet().removeIf(entry -> !ctx.isLocalEntity(entry.getKey())); + } + + @Override + public void destroy() { + if (lastActivityEventTimestamps != null) { + lastActivityEventTimestamps.clear(); + lastActivityEventTimestamps = null; + } + stopwatch = null; + event = null; + } + } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index e967ec49b5..af72927e3c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -15,6 +15,8 @@ */ package org.thingsboard.rule.engine.action; +import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,29 +28,35 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; 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.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; +import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import java.time.Duration; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -62,28 +70,33 @@ public class TbDeviceStateNodeTest { @Captor private static ArgumentCaptor callbackCaptor; private TbDeviceStateNode node; + private RuleNodeId nodeId; private TbDeviceStateNodeConfiguration config; private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final long METADATA_TS = System.currentTimeMillis(); + private TbMsg cleanupMsg; private TbMsg msg; + private long nowNanos; + private final Ticker controlledTicker = new Ticker() { + @Override + public long read() { + return nowNanos; + } + }; @BeforeEach public void setup() { - var device = new Device(); - device.setTenantId(TENANT_ID); - device.setId(DEVICE_ID); - device.setName("My humidity sensor"); - device.setType("Humidity sensor"); - device.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID())); var metaData = new TbMsgMetaData(); - metaData.putValue("deviceName", device.getName()); - metaData.putValue("deviceType", device.getType()); + metaData.putValue("deviceName", "My humidity sensor"); + metaData.putValue("deviceType", "Humidity sensor"); metaData.putValue("ts", String.valueOf(METADATA_TS)); var data = JacksonUtil.newObjectNode(); data.put("humidity", 58.3); - msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, device.getId(), metaData, JacksonUtil.toString(data)); + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, JacksonUtil.toString(data)); + nodeId = new RuleNodeId(UUID.randomUUID()); + cleanupMsg = TbMsg.newMsg(null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, nodeId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); } @BeforeEach @@ -99,17 +112,121 @@ public class TbDeviceStateNodeTest { @Test public void givenNullEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException() { - // GIVEN - config.setEvent(null); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - // WHEN-THEN - assertThatThrownBy(() -> node.init(ctxMock, nodeConfig)) + // GIVEN-WHEN-THEN + assertThatThrownBy(() -> initNode(null)) .isInstanceOf(TbNodeException.class) .hasMessage("Event cannot be null!") .matches(e -> ((TbNodeException) e).isUnrecoverable()); } + @Test + public void givenValidConfig_whenInit_thenSchedulesCleanupMsg() { + // GIVEN + given(ctxMock.getSelfId()).willReturn(nodeId); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + + // WHEN + try { + initNode(TbMsgType.ACTIVITY_EVENT); + } catch (Exception e) { + fail("Node failed to initialize."); + } + + // THEN + verifyCleanupMsgSent(); + } + + @Test + public void givenCleanupMsg_whenOnMsg_thenCleansStaleEntries() { + // GIVEN + given(ctxMock.getSelfId()).willReturn(nodeId); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + + ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); + + Stopwatch stopwatch = Stopwatch.createStarted(controlledTicker); + ReflectionTestUtils.setField(node, "stopwatch", stopwatch); + + // WHEN + Duration expirationTime = Duration.ofDays(1L); + + DeviceId staleId = DEVICE_ID; + Duration staleTs = Duration.ofHours(4L); + lastActivityEventTimestamps.put(staleId, staleTs); + + DeviceId goodId = new DeviceId(UUID.randomUUID()); + Duration goodTs = staleTs.plus(expirationTime); + lastActivityEventTimestamps.put(goodId, goodTs); + + nowNanos = staleTs.toNanos() + expirationTime.toNanos() + 1; + node.onMsg(ctxMock, cleanupMsg); + + // THEN + assertThat(lastActivityEventTimestamps) + .containsKey(goodId) + .doesNotContainKey(staleId) + .size().isOne(); + + verifyCleanupMsgSent(); + then(ctxMock).should().ack(cleanupMsg); + then(ctxMock).shouldHaveNoMoreInteractions(); + } + + @Test + public void givenMsgArrivedTooFast_whenOnMsg_thenRateLimitsThisMsg() { + // GIVEN + ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); + + Stopwatch stopwatch = Stopwatch.createStarted(controlledTicker); + ReflectionTestUtils.setField(node, "stopwatch", stopwatch); + + // WHEN + Duration firstEventTs = Duration.ofMillis(1000L); + lastActivityEventTimestamps.put(DEVICE_ID, firstEventTs); + + Duration tooFastEventTs = firstEventTs.plus(Duration.ofMillis(999L)); + nowNanos = tooFastEventTs.toNanos(); + node.onMsg(ctxMock, msg); + + // THEN + Duration actualEventTs = lastActivityEventTimestamps.get(DEVICE_ID); + assertThat(actualEventTs).isEqualTo(firstEventTs); + + then(ctxMock).should().tellSuccess(msg); + then(ctxMock).shouldHaveNoMoreInteractions(); + then(deviceStateManagerMock).shouldHaveNoInteractions(); + } + + @Test + public void givenHasNonLocalDevices_whenOnPartitionChange_thenRemovesEntriesForNonLocalDevices() { + // GIVEN + ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); + + lastActivityEventTimestamps.put(DEVICE_ID, Duration.ofHours(24L)); + given(ctxMock.isLocalEntity(eq(DEVICE_ID))).willReturn(true); + + DeviceId nonLocalDeviceId1 = new DeviceId(UUID.randomUUID()); + lastActivityEventTimestamps.put(nonLocalDeviceId1, Duration.ofHours(30L)); + given(ctxMock.isLocalEntity(eq(nonLocalDeviceId1))).willReturn(false); + + DeviceId nonLocalDeviceId2 = new DeviceId(UUID.randomUUID()); + lastActivityEventTimestamps.put(nonLocalDeviceId2, Duration.ofHours(32L)); + given(ctxMock.isLocalEntity(eq(nonLocalDeviceId2))).willReturn(false); + + // WHEN + node.onPartitionChangeMsg(ctxMock, new PartitionChangeMsg(ServiceType.TB_RULE_ENGINE)); + + // THEN + assertThat(lastActivityEventTimestamps) + .containsKey(DEVICE_ID) + .doesNotContainKey(nonLocalDeviceId1) + .doesNotContainKey(nonLocalDeviceId2) + .size().isOne(); + } + @ParameterizedTest @EnumSource( value = TbMsgType.class, @@ -117,12 +234,8 @@ public class TbDeviceStateNodeTest { mode = EnumSource.Mode.EXCLUDE ) public void givenUnsupportedEventInConfig_whenInit_thenThrowsUnrecoverableTbNodeException(TbMsgType unsupportedEvent) { - // GIVEN - config.setEvent(unsupportedEvent); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); - - // WHEN-THEN - assertThatThrownBy(() -> node.init(ctxMock, nodeConfig)) + // GIVEN-WHEN-THEN + assertThatThrownBy(() -> initNode(unsupportedEvent)) .isInstanceOf(TbNodeException.class) .hasMessage("Unsupported event: " + unsupportedEvent) .matches(e -> ((TbNodeException) e).isUnrecoverable()); @@ -156,17 +269,19 @@ public class TbDeviceStateNodeTest { @ParameterizedTest @MethodSource - public void givenInactivityEventAndDeviceOriginator_whenOnMsg_thenOnDeviceInactivityIsCalledWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { + public void givenSupportedEventAndDeviceOriginator_whenOnMsg_thenCorrectEventIsSentWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { // GIVEN - config.setEvent(supportedEventType); - var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + given(ctxMock.getSelfId()).willReturn(nodeId); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); + try { - node.init(ctxMock, nodeConfig); + initNode(supportedEventType); } catch (TbNodeException e) { fail("Node failed to initialize!", e); } - given(ctxMock.getTenantId()).willReturn(TENANT_ID); - given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); + verifyCleanupMsgSent(); // WHEN node.onMsg(ctxMock, msg); @@ -188,7 +303,7 @@ public class TbDeviceStateNodeTest { then(ctxMock).shouldHaveNoMoreInteractions(); } - private static Stream givenInactivityEventAndDeviceOriginator_whenOnMsg_thenOnDeviceInactivityIsCalledWithCorrectCallback() { + private static Stream givenSupportedEventAndDeviceOriginator_whenOnMsg_thenCorrectEventIsSentWithCorrectCallback() { return Stream.of( Arguments.of(TbMsgType.CONNECT_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceConnect(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())), Arguments.of(TbMsgType.ACTIVITY_EVENT, (Runnable) () -> then(deviceStateManagerMock).should().onDeviceActivity(eq(TENANT_ID), eq(DEVICE_ID), eq(METADATA_TS), callbackCaptor.capture())), @@ -197,4 +312,16 @@ public class TbDeviceStateNodeTest { ); } + private void initNode(TbMsgType event) throws TbNodeException { + config.setEvent(event); + var nodeConfig = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); + node.init(ctxMock, nodeConfig); + } + + private void verifyCleanupMsgSent() { + then(ctxMock).should().getSelfId(); + then(ctxMock).should().newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING)); + then(ctxMock).should().tellSelf(eq(cleanupMsg), eq(Duration.ofHours(1L).toMillis())); + } + } From b790e09211618db04775a11feef1fb4f7f15dcd4 Mon Sep 17 00:00:00 2001 From: Yevhen Popok Date: Tue, 30 Jan 2024 12:01:37 +0200 Subject: [PATCH 068/137] For Angular Gridster, set "maxRows" limit to "3000" --- .../modules/home/components/dashboard/dashboard.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 6fa26ee412..cbd5d692d0 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -211,7 +211,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo disableAutoPositionOnConflict: false, pushItems: false, swap: false, - maxRows: Infinity, + maxRows: 3000, minCols: this.columns ? this.columns : 24, maxCols: 3000, maxItemCols: 1000, From 2159430c1b50383a4463a8377bee3f6f8b6e9cb3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Tue, 30 Jan 2024 12:22:37 +0200 Subject: [PATCH 069/137] Fix test for message types with null rule node connections --- .../org/thingsboard/server/common/data/msg/TbMsgTypeTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index 9dc7110340..ebf717fb63 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -22,6 +22,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM; import static org.thingsboard.server.common.data.msg.TbMsgType.ALARM_DELETE; +import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.NA; import static org.thingsboard.server.common.data.msg.TbMsgType.DEDUPLICATION_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_SELF_MSG; @@ -53,6 +54,7 @@ class TbMsgTypeTest { DEDUPLICATION_TIMEOUT_SELF_MSG, DELAY_TIMEOUT_SELF_MSG, MSG_COUNT_SELF_MSG, + DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, NA ); From f35ce53fce3b0284d9c0708888f59ce730a5f2d3 Mon Sep 17 00:00:00 2001 From: Rhyaldir <150129324+Rhyaldir@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:13:57 +0100 Subject: [PATCH 070/137] Add mockito-inline to support mocking statics and final classes --- common/transport/lwm2m/pom.xml | 5 +++++ pom.xml | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 0f00bbb2c9..530000f95e 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -94,6 +94,11 @@ junit-vintage-engine test + + org.mockito + mockito-inline + test + org.awaitility awaitility diff --git a/pom.xml b/pom.xml index 6efb293c41..3a5e64db9e 100755 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,7 @@ 2.7.2 1.5.2 5.8.2 + 4.5.1 2.6.0 5.13.1 1.3.0 @@ -1649,6 +1650,12 @@ + + org.mockito + mockito-inline + ${mockito.version} + test + org.testng testng From 6614928bce91aa2afd15ea942b97cc82ea59128b Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 31 Jan 2024 17:06:49 +0200 Subject: [PATCH 071/137] UI: improvements for form scss --- ...uid-level-card-basic-config.component.html | 30 +++++++++---------- ...-level-card-widget-settings.component.html | 24 +++++++-------- ui-ngx/src/form.scss | 23 ++++++++++++++ 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index 4940df18ba..2653842e6d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -34,7 +34,7 @@ {{ 'widgets.liquid-level-card.title' | translate }} -
    +
    @@ -49,7 +49,7 @@ {{ 'widgets.liquid-level-card.icon' | translate }} -
    +
    @@ -64,7 +64,7 @@
    -
    +
    widgets.liquid-level-card.shape
    @@ -96,7 +96,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    widgets.liquid-level-card.units
    widgets.liquid-level-card.datasource-units
    -
    +
    -
    +
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -154,9 +154,9 @@
    -
    +
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -186,9 +186,9 @@
    -
    +
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -226,7 +226,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -241,7 +241,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -267,7 +267,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -288,7 +288,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html index d549198273..6e3967c67d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html @@ -19,7 +19,7 @@
    -
    +
    widgets.liquid-level-card.shape
    @@ -51,7 +51,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    -
    +
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -111,9 +111,9 @@
    -
    +
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -143,9 +143,9 @@
    -
    +
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -188,7 +188,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -198,7 +198,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -232,7 +232,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -252,7 +252,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index ee6d4167cd..8b857c05c8 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -211,6 +211,29 @@ } } + .tb-flex-centered-row { + display: flex; + flex: 1; + flex-direction: row; + align-items: center; + gap: 8px; + &.flex-start { + justify-content: flex-start; + } + &.flex-end { + justify-content: flex-end; + } + &.space-between { + justify-content: space-between; + } + &.no-gap { + gap: 0; + } + &.fill-width { + width: 100%; + } + } + .tb-form-panel, .tb-form-row { .mat-slide { margin: 0; From ac04e6c7ba6cc041f8f586dc1428fc603750fe65 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 31 Jan 2024 17:17:17 +0200 Subject: [PATCH 072/137] refactoring for tb-flex-row --- ...uid-level-card-basic-config.component.html | 24 +++++++++---------- ...-level-card-widget-settings.component.html | 20 ++++++++-------- ui-ngx/src/form.scss | 6 +++-- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index 2653842e6d..fd568ad021 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -34,7 +34,7 @@ {{ 'widgets.liquid-level-card.title' | translate }} -
    +
    @@ -49,7 +49,7 @@ {{ 'widgets.liquid-level-card.icon' | translate }} -
    +
    @@ -64,7 +64,7 @@
    -
    +
    widgets.liquid-level-card.shape
    @@ -96,7 +96,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    widgets.liquid-level-card.units
    widgets.liquid-level-card.datasource-units
    -
    +
    @@ -132,7 +132,7 @@
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -156,7 +156,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -188,7 +188,7 @@
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -226,7 +226,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -241,7 +241,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -267,7 +267,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -288,7 +288,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html index 6e3967c67d..8c5e761ea6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html @@ -19,8 +19,8 @@
    -
    -
    widgets.liquid-level-card.shape
    +
    +
    widgets.liquid-level-card.shape
    {{ DataSourceTypeTranslations.get(type) | translate }} @@ -51,7 +51,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -113,7 +113,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -145,7 +145,7 @@
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -188,7 +188,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -198,7 +198,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -232,7 +232,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -252,7 +252,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 8b857c05c8..b88a11979a 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -211,11 +211,10 @@ } } - .tb-flex-centered-row { + .tb-flex-row { display: flex; flex: 1; flex-direction: row; - align-items: center; gap: 8px; &.flex-start { justify-content: flex-start; @@ -226,6 +225,9 @@ &.space-between { justify-content: space-between; } + &.align-center { + align-items: center; + } &.no-gap { gap: 0; } From b298f5a52a327975665f3c4c61349f3488f69301 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 31 Jan 2024 17:43:57 +0200 Subject: [PATCH 073/137] refactoring for tb-flex --- ...uid-level-card-basic-config.component.html | 24 +++++++++---------- ...-level-card-widget-settings.component.html | 18 +++++++------- ui-ngx/src/form.scss | 9 +++++-- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html index fd568ad021..b00a89969b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html @@ -34,7 +34,7 @@ {{ 'widgets.liquid-level-card.title' | translate }} -
    +
    @@ -49,7 +49,7 @@ {{ 'widgets.liquid-level-card.icon' | translate }} -
    +
    @@ -64,7 +64,7 @@
    -
    +
    widgets.liquid-level-card.shape
    @@ -96,7 +96,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    widgets.liquid-level-card.units
    widgets.liquid-level-card.datasource-units
    -
    +
    @@ -132,7 +132,7 @@
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -156,7 +156,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -188,7 +188,7 @@
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -226,7 +226,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -241,7 +241,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -267,7 +267,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -288,7 +288,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html index 8c5e761ea6..0040e7fa64 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html @@ -19,7 +19,7 @@
    -
    +
    widgets.liquid-level-card.shape
    @@ -51,7 +51,7 @@ formControlName="shapeAttributeName">
    -
    +
    widgets.liquid-level-card.shape-by-attribute
    widgets.liquid-level-card.widget-units
    -
    +
    @@ -113,7 +113,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -145,7 +145,7 @@
    widgets.liquid-level-card.total-volume-units
    -
    +
    @@ -188,7 +188,7 @@
    widgets.liquid-level-card.value
    -
    +
    @@ -198,7 +198,7 @@
    widgets.liquid-level-card.total-volume
    -
    +
    @@ -232,7 +232,7 @@ {{ 'widgets.liquid-level-card.level' | translate }} -
    +
    @@ -252,7 +252,7 @@ {{ 'widgets.value-card.date' | translate }} -
    +
    diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index b88a11979a..eb26a95455 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -211,11 +211,16 @@ } } - .tb-flex-row { + .tb-flex { display: flex; flex: 1; - flex-direction: row; gap: 8px; + &.row { + flex-direction: row; + } + &.column { + flex-direction: column; + } &.flex-start { justify-content: flex-start; } From 99d8f9966b35b3ac21fc7ed59707654d3cbffb45 Mon Sep 17 00:00:00 2001 From: Rhyaldir <150129324+Rhyaldir@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:49:22 +0100 Subject: [PATCH 074/137] Fix: Registration is not found when using CID and address or port has changed --- .../store/TbLwM2mRedisRegistrationStore.java | 22 +- .../store/util/LwM2MIdentitySerDes.java | 63 +++++ .../TbLwM2mRedisRegistrationStoreTest.java | 265 ++++++++++++++++++ .../store/util/LwM2MIdentitySerDesTest.java | 77 +++++ 4 files changed, 422 insertions(+), 5 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDes.java create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStoreTest.java create mode 100644 common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDesTest.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index ff86e60490..23c7a57dd6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -29,7 +29,6 @@ import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.serialization.IdentitySerDes; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; @@ -45,6 +44,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.integration.redis.util.RedisLockRegistry; +import org.thingsboard.server.transport.lwm2m.server.store.util.LwM2MIdentitySerDes; import java.net.InetSocketAddress; import java.util.ArrayList; @@ -110,12 +110,18 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, long lifetimeGracePeriodInSec, int cleanLimit) { + this(connectionFactory, schedExecutor, cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, + new RedisLockRegistry(connectionFactory, "Registration")); + } + + public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, + long lifetimeGracePeriodInSec, int cleanLimit, RedisLockRegistry lockRegistry) { this.connectionFactory = connectionFactory; this.schedExecutor = schedExecutor; this.cleanPeriod = cleanPeriodInSec; this.cleanLimit = cleanLimit; this.gracePeriod = lifetimeGracePeriodInSec; - this.redisLock = new RedisLockRegistry(connectionFactory, "Registration"); + this.redisLock = lockRegistry; } /* *************** Redis Key utility function **************** */ @@ -173,7 +179,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { removeAddrIndex(connection, oldRegistration); } - if (!oldRegistration.getIdentity().equals(registration.getIdentity())) { + if (registrationsHaveDifferentIdentities(oldRegistration, registration)) { removeIdentityIndex(connection, oldRegistration); } // remove old observation @@ -231,7 +237,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { removeAddrIndex(connection, r); } - if (!r.getIdentity().equals(updatedRegistration.getIdentity())) { + if (registrationsHaveDifferentIdentities(r, updatedRegistration)) { removeIdentityIndex(connection, r); } @@ -402,6 +408,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto connection.zRem(EXP_EP, registration.getEndpoint().getBytes(UTF_8)); } + private boolean registrationsHaveDifferentIdentities(Registration first, Registration second){ + var first_identity_string = LwM2MIdentitySerDes.serialize(first.getIdentity()).toString(); + var second_identity_string = LwM2MIdentitySerDes.serialize(second.getIdentity()).toString(); + return !first_identity_string.equals(second_identity_string); + } + private byte[] toRegIdKey(String registrationId) { return toKey(REG_EP_REGID_IDX, registrationId); } @@ -411,7 +423,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } private byte[] toRegIdentityKey(Identity identity) { - return toKey(REG_EP_IDENTITY, IdentitySerDes.serialize(identity).toString()); + return toKey(REG_EP_IDENTITY, LwM2MIdentitySerDes.serialize(identity).toString()); } private byte[] toEndpointKey(String endpoint) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDes.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDes.java new file mode 100644 index 0000000000..4ffbfd82bb --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDes.java @@ -0,0 +1,63 @@ +/** + * 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.transport.lwm2m.server.store.util; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; +import org.apache.commons.lang3.NotImplementedException; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.util.Hex; + +import java.security.PublicKey; + +public class LwM2MIdentitySerDes { + + private static final String KEY_ADDRESS = "address"; + private static final String KEY_PORT = "port"; + private static final String KEY_ID = "id"; + private static final String KEY_CN = "cn"; + private static final String KEY_RPK = "rpk"; + protected static final String KEY_LWM2MIDENTITY_TYPE = "type"; + protected static final String LWM2MIDENTITY_TYPE_UNSECURE = "unsecure"; + protected static final String LWM2MIDENTITY_TYPE_PSK = "psk"; + protected static final String LWM2MIDENTITY_TYPE_X509 = "x509"; + protected static final String LWM2MIDENTITY_TYPE_RPK = "rpk"; + + public static JsonObject serialize(Identity identity) { + JsonObject o = Json.object(); + + if (identity.isPSK()) { + o.set(KEY_LWM2MIDENTITY_TYPE, LWM2MIDENTITY_TYPE_PSK); + o.set(KEY_ID, identity.getPskIdentity()); + } else if (identity.isRPK()) { + o.set(KEY_LWM2MIDENTITY_TYPE, LWM2MIDENTITY_TYPE_RPK); + PublicKey publicKey = identity.getRawPublicKey(); + o.set(KEY_RPK, Hex.encodeHexString(publicKey.getEncoded())); + } else if (identity.isX509()) { + o.set(KEY_LWM2MIDENTITY_TYPE, LWM2MIDENTITY_TYPE_X509); + o.set(KEY_CN, identity.getX509CommonName()); + } else { + o.set(KEY_LWM2MIDENTITY_TYPE, LWM2MIDENTITY_TYPE_UNSECURE); + o.set(KEY_ADDRESS, identity.getPeerAddress().getHostString()); + o.set(KEY_PORT, identity.getPeerAddress().getPort()); + } + return o; + } + + public static Identity deserialize(JsonObject peer) { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStoreTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStoreTest.java new file mode 100644 index 0000000000..e09f2205b7 --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStoreTest.java @@ -0,0 +1,265 @@ +/** + * 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.transport.lwm2m.server.store; + +import org.eclipse.leshan.core.link.Link; +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.integration.redis.util.RedisLockRegistry; +import org.springframework.test.util.ReflectionTestUtils; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.locks.Lock; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mRedisRegistrationStore.DEFAULT_CLEAN_LIMIT; +import static org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mRedisRegistrationStore.DEFAULT_CLEAN_PERIOD; +import static org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mRedisRegistrationStore.DEFAULT_GRACE_PERIOD; + + +@ExtendWith(MockitoExtension.class) +class TbLwM2mRedisRegistrationStoreTest { + + RedisConnectionFactory connectionFactory; + RedisConnection connection; + RedisLockRegistry lockRegistry; + + TbLwM2mRedisRegistrationStore registrationStore; + + @BeforeEach + void setUp() { + lockRegistry = mock(RedisLockRegistry.class); + lenient().when(lockRegistry.obtain(any())).thenReturn(mock(Lock.class)); + connection = mock(RedisConnection.class); + //when(connection.set(any(byte[].class), any(byte[].class))). + connectionFactory = mock(RedisConnectionFactory.class); + lenient().when(connectionFactory.getConnection()).thenReturn(connection); + ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, + new NamedThreadFactory(String.format("RedisRegistrationStore Cleaner (%ds)", DEFAULT_CLEAN_PERIOD))); + registrationStore = new TbLwM2mRedisRegistrationStore(connectionFactory, executorService, + DEFAULT_CLEAN_PERIOD, DEFAULT_GRACE_PERIOD, DEFAULT_CLEAN_LIMIT, lockRegistry); + } + + @Test + void testAddRegistrationWithNoOldRegistration() { + setOldRegistration(null); + Registration registration = buildRegistration(); + + assertThat(registrationStore.addRegistration(registration)).isNull(); + + byte[] endpoint = registration.getEndpoint().getBytes(UTF_8); + verify(connection, times(1)).set(getRegIdKey(registration), endpoint); + verify(connection, times(1)).set(getRegAddrKey(registration), endpoint); + verify(connection, times(1)).set(getRegIdentityKey(registration), endpoint); + verify(connection, times(3)).set(any(byte[].class), any(byte[].class)); + verify(connection, times(0)).del(any(byte[].class)); + } + + @Test + void testAddRegistrationWithOldRegistrationEqualToCurrent(){ + var oldRegistration = buildRegistration(); + setOldRegistration(oldRegistration); + Registration registration = buildRegistration(); + + var deregistration = registrationStore.addRegistration(registration); + + assertThat(deregistration.getRegistration()).isEqualTo(oldRegistration); + + byte[] endpoint = registration.getEndpoint().getBytes(UTF_8); + verify(connection, times(1)).set(getRegIdKey(registration), endpoint); + verify(connection, times(1)).set(getRegAddrKey(registration), endpoint); + verify(connection, times(1)).set(getRegIdentityKey(registration), endpoint); + verify(connection, times(3)).set(any(byte[].class), any(byte[].class)); + verify(connection, times(1)).del(getTknsRegIdKey(oldRegistration)); + verify(connection, times(1)).del(any(byte[].class)); + } + + @Test + void testAddRegistrationRemovesIndexes(){ + var oldRegistration = buildRegistration(Identity.unsecure(getTestAddress(1234))); + setOldRegistration(oldRegistration); + var registration = buildRegistration(Identity.unsecure(getTestAddress(2345))); + + var deregistration = registrationStore.addRegistration(registration); + + assertThat(deregistration.getRegistration()).isEqualTo(oldRegistration); + byte[] endpoint = registration.getEndpoint().getBytes(UTF_8); + verify(connection, times(1)).set(getRegIdKey(registration), endpoint); + verify(connection, times(1)).set(getRegAddrKey(registration), endpoint); + verify(connection, times(1)).set(getRegIdentityKey(registration), endpoint); + verify(connection, times(3)).set(any(byte[].class), any(byte[].class)); + verify(connection, times(1)).del(getRegAddrKey(oldRegistration)); + verify(connection, times(1)).del(getRegIdentityKey(oldRegistration)); + verify(connection, times(1)).del(getTknsRegIdKey(oldRegistration)); + verify(connection, times(3)).del(any(byte[].class)); + } + + @Test + void testUpdateRegistrationWhenNoRegistrationFound() { + setOldRegistration(null); + Registration registration = buildRegistration(); + RegistrationUpdate update = createUpdateFromRegistration(registration); + + assertThat(registrationStore.updateRegistration(update)).isNull(); + + verify(connection, times(1)).get(getRegIdKey(registration)); + verify(connection, times(1)).get(any(byte[].class)); + verify(connection, times(0)).del(any(byte[].class)); + } + + @Test + void testUpdateRegistrationWithSameRegistration() { + Registration registration = buildRegistration(); + setOldRegistration(registration); + RegistrationUpdate update = createUpdateFromRegistration(registration); + + assertThat(registrationStore.updateRegistration(update)).isNotNull(); + + var endpoint = registration.getEndpoint().getBytes(UTF_8); + // check registration and addressIndex here updated + verify(connection, times(1)).set(eq(getEndpointKey(endpoint)), any(byte[].class)); + verify(connection, times(1)).set(getRegAddrKey(registration), endpoint); + verify(connection, times(2)).set(any(byte[].class), any(byte[].class)); + verify(connection, times(0)).del(any(byte[].class)); + } + + @Test + void testUpdateRegistrationWithRegistrationFromSecureIdentitiesWithDifferentAddress() { + Registration oldRegistration = buildRegistration(Identity.psk(getTestAddress(1234), "my:psk")); + setOldRegistration(oldRegistration); + Registration newRegistration = buildRegistration(Identity.psk(getTestAddress(2345), "my:psk")); + RegistrationUpdate update = createUpdateFromRegistration(newRegistration); + assertThat(oldRegistration.getEndpoint()).isEqualTo(newRegistration.getEndpoint()); + + assertThat(registrationStore.updateRegistration(update)).isNotNull(); + + var endpoint = newRegistration.getEndpoint().getBytes(UTF_8); + // check registration and addressIndex here updated + verify(connection, times(1)).set(eq(getEndpointKey(endpoint)), any(byte[].class)); + verify(connection, times(1)).set(getRegAddrKey(newRegistration), endpoint); + // check old AddrIndex has been removed + verify(connection, times(1)).del(getRegAddrKey(oldRegistration)); + // check identityIndex has not been removed + verify(connection, times(0)).del(getRegIdentityKey(oldRegistration)); + // check only one key (AddrIndex) in total was removed + verify(connection, times(1)).del(any(byte[].class)); + } + + @Test + void testGetRegistrationByIdentityReturnsRegistrationForSecureIdentityWithDifferentAddress() { + Registration registration = buildRegistration(Identity.psk(getTestAddress(1234), "my:psk")); + setOldRegistration(registration); + Identity sameIdentityWithDifferentAddress = Identity.psk(getTestAddress(2345), "my:psk"); + + Registration retrievedRegistration = registrationStore.getRegistrationByIdentity(sameIdentityWithDifferentAddress); + + assertThat(retrievedRegistration).isEqualTo(registration); + } + + private void setOldRegistration(Registration oldRegistration){ + byte[] serializedRegistration = null; + if (oldRegistration != null){ + byte[] endpoint = oldRegistration.getEndpoint().getBytes(UTF_8); + // set the AddrIndex + byte[] regAddrKey = getRegAddrKey(oldRegistration); + lenient().when(connection.get(eq(regAddrKey))).thenReturn(endpoint); + // set the IdentityIndex + byte[] regIdentityKey = getRegIdentityKey(oldRegistration); + lenient().when(connection.get(eq(regIdentityKey))).thenReturn(endpoint); + // set the IdIndex + byte[] regIdKey = getRegIdKey(oldRegistration); + lenient().when(connection.get(eq(regIdKey))).thenReturn(endpoint); + // set the registration + serializedRegistration = RegistrationSerDes.bSerialize(oldRegistration); + lenient().when(connection.get(eq(getEndpointKey(endpoint)))).thenReturn(serializedRegistration); + } + lenient().when(connection.getSet(any(byte[].class), any(byte[].class))).thenReturn(serializedRegistration); + } + + private byte[] getRegAddrKey(Registration registration){ + return ReflectionTestUtils.invokeMethod(registrationStore, "toRegAddrKey", registration.getSocketAddress()); + } + + private byte[] getRegIdentityKey(Registration registration){ + return ReflectionTestUtils.invokeMethod(registrationStore, "toRegIdentityKey", registration.getIdentity()); + } + + private byte[] getRegIdKey(Registration registration){ + return ReflectionTestUtils.invokeMethod(registrationStore, "toRegIdKey", registration.getId()); + } + + private byte[] getEndpointKey(byte[] endpoint){ + return ReflectionTestUtils.invokeMethod(registrationStore, "toEndpointKey", endpoint); + } + + private byte[] getTknsRegIdKey(Registration registration){ + return ReflectionTestUtils.invokeMethod(registrationStore, "toKey", "TKNS:REGID:", registration.getId()); + } + + private static Registration buildRegistration() { + return buildRegistration(Identity.psk(getTestAddress(), "my:psk")); + } + + private static Registration buildRegistration(Identity identity){ + return new Registration.Builder("my_reg_id", "abcde", identity) + .objectLinks(new Link[]{}) + .build(); + } + + private static RegistrationUpdate createUpdateFromRegistration(Registration registration){ + return new RegistrationUpdate( + registration.getId(), + registration.getIdentity(), + registration.getLifeTimeInSec(), + registration.getSmsNumber(), + registration.getBindingMode(), + registration.getObjectLinks(), + registration.getAdditionalRegistrationAttributes() + ); + } + + private static InetSocketAddress getTestAddress() { + return getTestAddress(5684); + } + + private static InetSocketAddress getTestAddress(int port) { + try { + return new InetSocketAddress(InetAddress.getByName("1.2.3.4"), port); + } catch (UnknownHostException e) { + throw new AssertionError("Cannot create test address"); + } + } +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDesTest.java b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDesTest.java new file mode 100644 index 0000000000..c75ed7a195 --- /dev/null +++ b/common/transport/lwm2m/src/test/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MIdentitySerDesTest.java @@ -0,0 +1,77 @@ +/** + * 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.transport.lwm2m.server.store.util; + +import com.eclipsesource.json.JsonObject; +import org.apache.commons.lang3.NotImplementedException; +import org.eclipse.leshan.core.request.Identity; +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.security.PublicKey; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class LwM2MIdentitySerDesTest { + + @Test + void serializePskIdentity() { + assertThat(LwM2MIdentitySerDes.serialize(Identity.psk(getTestAddress(), "my:psk")).toString()) + .isEqualTo("{\"type\":\"psk\",\"id\":\"my:psk\"}"); + } + + + @Test + void serializeRpkIdentity() { + var public_key = mock(PublicKey.class); + when(public_key.getEncoded()).thenReturn(new byte[]{1,2,3,4,5,6,7,8,9}); + + assertThat(LwM2MIdentitySerDes.serialize(Identity.rpk(getTestAddress(), public_key)).toString()) + .isEqualTo("{\"type\":\"rpk\",\"rpk\":\"010203040506070809\"}"); + } + + @Test + void serializeX509Identity() { + assertThat(LwM2MIdentitySerDes.serialize(Identity.x509(getTestAddress(), "MyCommonName")).toString()) + .isEqualTo("{\"type\":\"x509\",\"cn\":\"MyCommonName\"}"); + } + + @Test + void serializeUnsecureIdentity() { + assertThat(LwM2MIdentitySerDes.serialize(Identity.unsecure(getTestAddress())).toString()) + .isEqualTo("{\"type\":\"unsecure\",\"address\":\"1.2.3.4\",\"port\":5684}"); + } + + + @Test + void deserialize() { + assertThatThrownBy(() -> LwM2MIdentitySerDes.deserialize(mock(JsonObject.class))) + .isInstanceOf(NotImplementedException.class); + } + + private static InetSocketAddress getTestAddress() { + try { + return new InetSocketAddress(InetAddress.getByName("1.2.3.4"), 5684); + } catch (UnknownHostException e) { + throw new AssertionError("Cannot create test address"); + } + } +} \ No newline at end of file From 94bca91b7b2fd13ec222a283b97094801296d590 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 31 Jan 2024 18:33:41 +0200 Subject: [PATCH 075/137] UI: added queueName to rule-node copy buffer --- ui-ngx/src/app/core/services/item-buffer.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index b928245765..e1941590ea 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -312,6 +312,9 @@ export class ItemBufferService { if (isDefinedAndNotNull(origNode.singletonMode)) { node.singletonMode = origNode.singletonMode; } + if (isDefinedAndNotNull(origNode.queueName)) { + node.queueName = origNode.queueName; + } ruleNodes.nodes.push(node); if (i === 0) { top = node.y; From 959704f16d9df26dcd5239bbb999f99bab8f1dc6 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 1 Feb 2024 12:12:02 +0200 Subject: [PATCH 076/137] UI: added improvements and refactoring for snmp --- ...ce-profile-communication-config.component.scss | 15 ++++++++------- .../snmp-device-profile-mapping.component.html | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss index 870b627378..e7877699b5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-communication-config.component.scss @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import '../scss/constants'; + :host { .communication-config { border: 2px groove rgba(0, 0, 0, 0.25); border-radius: 4px; padding: 8px; min-width: 0; - flex-direction: row; - box-sizing: border-box; + flex-direction: column; display: flex; place-content: stretch flex-start; align-items: stretch; - flex: 1 1 0; - gap: 8px; + flex: 1; + gap: 0; } .scope-row { @@ -36,10 +37,10 @@ margin: 16px 0 } - @media (max-width: 1650px) { + @media #{$mat-gt-xmd} { .communication-config { - flex-direction: column; - gap: 0; + flex-direction: row; + gap: 8px; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html index 94330f7c23..8b4b656c02 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html @@ -19,8 +19,8 @@
    - - + +
    From 9b32a51d4b100d26f976b52e73a83ec380f18722 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 1 Feb 2024 13:46:31 +0200 Subject: [PATCH 077/137] Refactoring due to review. Fix naming, improve upgrade logic --- .../server/service/edge/rpc/EdgeGrpcSession.java | 4 +++- .../install/DefaultSystemDataLoaderService.java | 3 +++ .../notification/DefaultNotificationCenter.java | 10 ++++++---- .../EdgeCommunicationFailureTriggerProcessor.java | 2 +- .../impl/NotificationRuleExportService.java | 7 ++++--- .../EdgeCommunicationFailureNotificationInfo.java | 1 - .../info/EdgeConnectionNotificationInfo.java | 1 - .../trigger/EdgeCommunicationFailureTrigger.java | 2 +- .../rule/trigger/EdgeConnectionTrigger.java | 4 ++-- .../server/dao/edge/BaseEdgeEventService.java | 7 ++++--- .../DefaultNotificationSettingsService.java | 13 ++++++++----- .../notification/edge_communication_failure.md | 2 +- .../help/en_US/notification/edge_connection.md | 2 +- 13 files changed, 34 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 44bdfcdb1c..b61d7d5e95 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -479,11 +479,13 @@ public final class EdgeGrpcSession implements Closeable { log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { if (this.clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > this.clientMaxInboundMessageSize) { + String error = String.format("Client max inbound message size [{%s}] is exceeded. Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE " + + "env variable on the edge and restart it.", this.clientMaxInboundMessageSize); String message = String.format("Downlink msg size [{%s}] exceeds client max inbound message size [{%s}]. " + "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), this.clientMaxInboundMessageSize); log.error("[{}][{}][{}] {} Message {}", this.tenantId, edge.getId(), this.sessionId, message, downlinkMsg); ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) - .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(message).build()); + .edgeId(edge.getId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(error).build()); sessionState.getPendingMsgsMap().remove(downlinkMsg.getDownlinkMsgId()); } else { sendDownlinkMsg(ResponseMsg.newBuilder() diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index fcc829886c..e50da5ea62 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -696,6 +696,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } @Override + @SneakyThrows public void updateDefaultNotificationConfigs() { PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500); ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4)); @@ -710,6 +711,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } }); } + executor.shutdown(); + executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); notificationSettingsService.updateDefaultNotificationConfigs(TenantId.SYS_TENANT_ID); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 5bbde6de81..be37d98bb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -241,11 +241,13 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple case PLATFORM_USERS: { PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration(); if (targetConfig.getUsersFilter().getType().isForRules() && ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) { - recipients = new PageDataIterable<>(pageLink -> - notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink), 500); + recipients = new PageDataIterable<>(pageLink -> { + return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink); + }, 500); } else { - recipients = new PageDataIterable<>(pageLink -> - notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink), 500); + recipients = new PageDataIterable<>(pageLink -> { + return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink); + }, 500); } break; } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java index dc54024eec..38d8ae9805 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EdgeCommunicationFailureTriggerProcessor.java @@ -53,7 +53,7 @@ public class EdgeCommunicationFailureTriggerProcessor implements NotificationRul } private String truncateFailureMsg(String input) { - int maxLength = 800; + int maxLength = 500; if (input != null && input.length() > maxLength) { return input.substring(0, maxLength); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java index 40d7d7c2b5..35f2489f83 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/NotificationRuleExportService.java @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.sync.ie.EntityExportData; @@ -92,8 +92,9 @@ public class NotificationRuleExportService> escalationTable = new LinkedHashMap<>(recipientsConfig.getEscalationTable()); - escalationTable.replaceAll((delay, targets) -> - toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList())); + escalationTable.replaceAll((delay, targets) -> { + return toExternalIds(targets, NotificationTargetId::new, ctx).collect(Collectors.toList()); + }); recipientsConfig.setEscalationTable(escalationTable); break; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeCommunicationFailureNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeCommunicationFailureNotificationInfo.java index 2edab592d5..6d0f2ae5f5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeCommunicationFailureNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeCommunicationFailureNotificationInfo.java @@ -43,7 +43,6 @@ public class EdgeCommunicationFailureNotificationInfo implements RuleOriginatedN @Override public Map getTemplateData() { return mapOf( - "tenantId", tenantId.toString(), "edgeId", edgeId.toString(), "edgeName", edgeName, "failureMsg", failureMsg diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java index e7b6494fdb..62b1370566 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EdgeConnectionNotificationInfo.java @@ -44,7 +44,6 @@ public class EdgeConnectionNotificationInfo implements RuleOriginatedNotificatio public Map getTemplateData() { return mapOf( "eventType", eventType, - "tenantId", tenantId.toString(), "edgeId", edgeId.toString(), "edgeName", edgeName ); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java index 6400a5c67a..6212c22a0d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeCommunicationFailureTrigger.java @@ -43,7 +43,7 @@ public class EdgeCommunicationFailureTrigger implements NotificationRuleTrigger @Override public String getDeduplicationKey() { - return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeId.toString(), error); + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), error); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java index fe5f6fa6b1..766338dba6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EdgeConnectionTrigger.java @@ -42,12 +42,12 @@ public class EdgeConnectionTrigger implements NotificationRuleTrigger { @Override public String getDeduplicationKey() { - return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), edgeName, String.valueOf(connected)); + return String.join(":", NotificationRuleTrigger.super.getDeduplicationKey(), String.valueOf(connected)); } @Override public long getDefaultDeduplicationDuration() { - return TimeUnit.MINUTES.toMillis(30); + return TimeUnit.MINUTES.toMillis(1); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index 6a05b98d16..43c3dc4914 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -41,9 +41,10 @@ public class BaseEdgeEventService implements EdgeEventService { @Override public ListenableFuture saveAsync(EdgeEvent edgeEvent) { - boolean isEdgeEventTenantRateLimitReached = !rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS, edgeEvent.getTenantId()); - boolean isEdgeEventRateLimitPerEdgeReached = !rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS_PER_EDGE, edgeEvent.getTenantId(), edgeEvent.getEdgeId()); - if (isEdgeEventTenantRateLimitReached || isEdgeEventRateLimitPerEdgeReached) { + if (!rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS, edgeEvent.getTenantId())) { + throw new TbRateLimitsException(EntityType.TENANT); + } + if (!rateLimitService.checkRateLimit(LimitedApi.EDGE_EVENTS_PER_EDGE, edgeEvent.getTenantId(), edgeEvent.getEdgeId())) { throw new TbRateLimitsException(EntityType.EDGE); } edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java index b42fdd7645..c858eecf62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java @@ -210,19 +210,22 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS defaultNotifications.create(tenantId, DefaultNotifications.exceededPerEntityRateLimits, affectedTenantAdmins.getId()); defaultNotifications.create(tenantId, DefaultNotifications.exceededRateLimitsForSysadmin, sysAdmins.getId()); } else { - List requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); - List existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( + var requiredNotificationTypes = List.of(NotificationType.EDGE_CONNECTION, NotificationType.EDGE_COMMUNICATION_FAILURE); + var existingNotificationTypes = notificationTemplateService.findNotificationTemplatesByTenantIdAndNotificationTypes( tenantId, requiredNotificationTypes, new PageLink(1)) .getData() .stream() .map(NotificationTemplate::getNotificationType) - .collect(Collectors.toList()); + .collect(Collectors.toSet()); + + if (existingNotificationTypes.containsAll(requiredNotificationTypes)) { + return; + } NotificationTarget tenantAdmins = notificationTargetService.findNotificationTargetsByTenantIdAndUsersFilterType(tenantId, UsersFilterType.TENANT_ADMINISTRATORS) .stream() .findFirst() - .orElseGet(() -> createTarget(tenantId, "Tenant administrators", new TenantAdministratorsFilter(), - tenantId.isSysTenantId() ? "All tenant administrators" : "Tenant administrators")); + .orElseGet(() -> createTarget(tenantId, "Tenant administrators", new TenantAdministratorsFilter(), "Tenant administrators")); for (NotificationType type : requiredNotificationTypes) { if (!existingNotificationTypes.contains(type)) { diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md index 03f2a2816a..712f2b45e7 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_communication_failure.md @@ -1,4 +1,4 @@ -#### Edge notification templatization +#### Edge communication failure notification templatization

    diff --git a/ui-ngx/src/assets/help/en_US/notification/edge_connection.md b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md index fed6c0372e..37f0ec7573 100644 --- a/ui-ngx/src/assets/help/en_US/notification/edge_connection.md +++ b/ui-ngx/src/assets/help/en_US/notification/edge_connection.md @@ -1,4 +1,4 @@ -#### Edge notification templatization +#### Edge connection notification templatization

    From 61ce68854deacbf71e6aaff1a33dcb84471e8c85 Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 1 Feb 2024 13:59:08 +0200 Subject: [PATCH 078/137] UI: fixed chart card value color differs from other widgets with same values and range colors configs --- .../widget/lib/cards/aggregated-value-card-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index af3bd41caa..dc3b095552 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -242,7 +242,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit aggValue.value = 'N/A'; } const numeric = formatNumberValue(value, (aggValue.key.decimals || this.ctx.decimals)); - aggValue.color.update(numeric); + aggValue.color.update(value); if (aggValue.showArrow && isDefined(numeric)) { aggValue.upArrow = numeric > 0; aggValue.downArrow = numeric < 0; From bf9c4af2aa738655dcbfc5dabde934184b38cbc2 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 1 Feb 2024 20:23:12 +0100 Subject: [PATCH 079/137] fixed infinit tell failure --- .../AbstractTbRuleEngineSubmitStrategy.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java index 3801e43b7f..6f9a4c49c6 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.queue.processing; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -51,7 +52,18 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine List> newOrderedMsgList = new ArrayList<>(reprocessMap.size()); for (IdMsgPair pair : orderedMsgList) { if (reprocessMap.containsKey(pair.uuid)) { - newOrderedMsgList.add(pair); + var oldValue = pair.getMsg().getValue(); + if (StringUtils.isNotEmpty(oldValue.getFailureMessage())) { + var newValue = TransportProtos.ToRuleEngineMsg.newBuilder() + .setTenantIdMSB(oldValue.getTenantIdMSB()) + .setTenantIdLSB(oldValue.getTenantIdLSB()) + .setTbMsg(oldValue.getTbMsg()) + .build(); + var newMsg = new TbProtoQueueMsg<>(pair.getMsg().getKey(), newValue, pair.getMsg().getHeaders()); + newOrderedMsgList.add(new IdMsgPair<>(pair.getUuid(), newMsg)); + } else { + newOrderedMsgList.add(pair); + } } } orderedMsgList = newOrderedMsgList; From 691a6b5d6a1cc0acd300eca5d9396e296207bf82 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 2 Feb 2024 10:52:38 +0200 Subject: [PATCH 080/137] Polishing after review --- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../server/common/data/msg/TbMsgType.java | 2 +- .../server/common/data/msg/TbMsgTypeTest.java | 16 ++++++++-------- .../rule/engine/action/TbDeviceStateNode.java | 15 +++++++-------- .../engine/action/TbDeviceStateNodeTest.java | 11 +++++------ 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 9942476c01..6db0e16f28 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -199,7 +199,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService typesWithNullRuleNodeConnection = List.of( ALARM, ALARM_DELETE, @@ -54,7 +54,7 @@ class TbMsgTypeTest { DEDUPLICATION_TIMEOUT_SELF_MSG, DELAY_TIMEOUT_SELF_MSG, MSG_COUNT_SELF_MSG, - DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, + DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, NA ); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index a74df13f20..9d6901ea01 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -69,6 +69,7 @@ public class TbDeviceStateNode implements TbNode { ); private static final Duration ONE_SECOND = Duration.ofSeconds(1L); private static final Duration ENTRY_EXPIRATION_TIME = Duration.ofDays(1L); + private static final Duration ENTRY_CLEANUP_PERIOD = Duration.ofHours(1L); private Stopwatch stopwatch; private ConcurrentMap lastActivityEventTimestamps; @@ -91,10 +92,9 @@ public class TbDeviceStateNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.isTypeOf(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG)) { + if (msg.isTypeOf(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG)) { removeStaleEntries(); scheduleCleanupMsg(ctx); - ctx.ack(msg); return; } @@ -109,7 +109,7 @@ public class TbDeviceStateNode implements TbNode { Duration now = stopwatch.elapsed(); if (lastEventTs == null) { - sendEvent(ctx, originator, msg); + sendEventAndTell(ctx, originator, msg); return now; } @@ -119,17 +119,16 @@ public class TbDeviceStateNode implements TbNode { return lastEventTs; } - sendEvent(ctx, originator, msg); - + sendEventAndTell(ctx, originator, msg); return now; }); } private void scheduleCleanupMsg(TbContext ctx) { TbMsg cleanupMsg = ctx.newMsg( - null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING + null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING ); - ctx.tellSelf(cleanupMsg, Duration.ofHours(1L).toMillis()); + ctx.tellSelf(cleanupMsg, ENTRY_CLEANUP_PERIOD.toMillis()); } private void removeStaleEntries() { @@ -141,7 +140,7 @@ public class TbDeviceStateNode implements TbNode { }); } - private void sendEvent(TbContext ctx, DeviceId originator, TbMsg msg) { + private void sendEventAndTell(TbContext ctx, DeviceId originator, TbMsg msg) { TenantId tenantId = ctx.getTenantId(); long eventTs = msg.getMetaDataTs(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index af72927e3c..1ce49b3a7b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -96,7 +96,7 @@ public class TbDeviceStateNodeTest { data.put("humidity", 58.3); msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, JacksonUtil.toString(data)); nodeId = new RuleNodeId(UUID.randomUUID()); - cleanupMsg = TbMsg.newMsg(null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG, nodeId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); + cleanupMsg = TbMsg.newMsg(null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, nodeId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); } @BeforeEach @@ -123,7 +123,7 @@ public class TbDeviceStateNodeTest { public void givenValidConfig_whenInit_thenSchedulesCleanupMsg() { // GIVEN given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); // WHEN try { @@ -140,7 +140,7 @@ public class TbDeviceStateNodeTest { public void givenCleanupMsg_whenOnMsg_thenCleansStaleEntries() { // GIVEN given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); @@ -169,7 +169,6 @@ public class TbDeviceStateNodeTest { .size().isOne(); verifyCleanupMsgSent(); - then(ctxMock).should().ack(cleanupMsg); then(ctxMock).shouldHaveNoMoreInteractions(); } @@ -272,7 +271,7 @@ public class TbDeviceStateNodeTest { public void givenSupportedEventAndDeviceOriginator_whenOnMsg_thenCorrectEventIsSentWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { // GIVEN given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); given(ctxMock.getTenantId()).willReturn(TENANT_ID); given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); @@ -320,7 +319,7 @@ public class TbDeviceStateNodeTest { private void verifyCleanupMsgSent() { then(ctxMock).should().getSelfId(); - then(ctxMock).should().newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING)); + then(ctxMock).should().newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING)); then(ctxMock).should().tellSelf(eq(cleanupMsg), eq(Duration.ofHours(1L).toMillis())); } From 46dca971e119c031f63cd5fab40390d6cf9576a0 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 2 Feb 2024 11:19:20 +0200 Subject: [PATCH 081/137] Add check to ensure that received inactivity time is not outdated relative to current activity time --- .../state/DefaultDeviceStateService.java | 8 ++++- .../state/DefaultDeviceStateServiceTest.java | 32 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index b400b4f65a..30c59b3da8 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -327,10 +327,16 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService Date: Fri, 2 Feb 2024 14:45:28 +0200 Subject: [PATCH 082/137] updated CassandraBaseTimeseriesDao to do one db insert instead of 5 when setNullValuesEnabled=true --- .../CassandraBaseTimeseriesDao.java | 142 ++++++++++-------- .../timeseries/BaseTimeseriesServiceTest.java | 35 ++++- ...eseriesServiceNoSqlSetNullEnabledTest.java | 72 +++++++++ .../nosql/TimeseriesServiceNoSqlTest.java | 96 ++++++++++++ 4 files changed, 281 insertions(+), 64 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlSetNullEnabledTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 31e29434e8..8d96dd293b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -65,6 +65,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -117,6 +118,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD private PreparedStatement[] fetchStmtsAsc; private PreparedStatement[] fetchStmtsDesc; private PreparedStatement deleteStmt; + private PreparedStatement saveWithNullStmt; + private PreparedStatement saveWithNullWithTtlStmt; private final Lock stmtCreationLock = new ReentrantLock(); private boolean isInstall() { @@ -159,19 +162,36 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD ttl = computeTtl(ttl); int dataPointDays = tsKvEntry.getDataPoints() * Math.max(1, (int) (ttl / SECONDS_IN_DAY)); long partition = toPartitionTs(tsKvEntry.getTs()); + String entityType = entityId.getEntityType().name(); + UUID entityIdId = entityId.getId(); + String entryKey = tsKvEntry.getKey(); + long ts = tsKvEntry.getTs(); DataType type = tsKvEntry.getDataType(); + BoundStatementBuilder stmtBuilder; if (setNullValuesEnabled) { - processSetNullValues(tenantId, entityId, tsKvEntry, ttl, futures, partition, type); - } - BoundStatementBuilder stmtBuilder = new BoundStatementBuilder((ttl == 0 ? getSaveStmt(type) : getSaveTtlStmt(type)).bind()); - stmtBuilder.setString(0, entityId.getEntityType().name()) - .setUuid(1, entityId.getId()) - .setString(2, tsKvEntry.getKey()) - .setLong(3, partition) - .setLong(4, tsKvEntry.getTs()); - addValue(tsKvEntry, stmtBuilder, 5); - if (ttl > 0) { - stmtBuilder.setInt(6, (int) ttl); + Boolean booleanValue = tsKvEntry.getBooleanValue().orElse(null); + String strValue = tsKvEntry.getStrValue().orElse(null); + Long longValue = tsKvEntry.getLongValue().orElse(null); + Double doubleValue = tsKvEntry.getDoubleValue().orElse(null); + String jsonValue = tsKvEntry.getJsonValue().orElse(null); + if (ttl == 0) { + stmtBuilder = new BoundStatementBuilder(getSaveWithNullStmt() + .bind(entityType, entityIdId, entryKey, partition, ts, booleanValue, strValue, longValue, doubleValue, jsonValue)); + } else { + stmtBuilder = new BoundStatementBuilder(getSaveWithNullWithTtlStmt() + .bind(entityType, entityIdId, entryKey, partition, ts, booleanValue, strValue, longValue, doubleValue, jsonValue, (int) ttl)); + } + } else { + stmtBuilder = new BoundStatementBuilder((ttl == 0 ? getSaveStmt(type) : getSaveTtlStmt(type)).bind()); + stmtBuilder.setString(0, entityType) + .setUuid(1, entityIdId) + .setString(2, entryKey) + .setLong(3, partition) + .setLong(4, ts); + addValue(tsKvEntry, stmtBuilder, 5); + if (ttl > 0) { + stmtBuilder.setInt(6, (int) ttl); + } } BoundStatement stmt = stmtBuilder.build(); futures.add(getFuture(executeAsyncWrite(tenantId, stmt), rs -> null)); @@ -449,56 +469,6 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD return tsFormat.getTruncateUnit().equals(ChronoUnit.FOREVER); } - private void processSetNullValues(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl, List> futures, long partition, DataType type) { - switch (type) { - case LONG: - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); - break; - case BOOLEAN: - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); - break; - case DOUBLE: - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); - break; - case STRING: - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); - break; - case JSON: - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); - futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); - break; - } - } - - private ListenableFuture saveNull(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl, long partition, DataType type) { - BoundStatementBuilder stmtBuilder = new BoundStatementBuilder((ttl == 0 ? getSaveStmt(type) : getSaveTtlStmt(type)).bind()); - stmtBuilder.setString(0, entityId.getEntityType().name()) - .setUuid(1, entityId.getId()) - .setString(2, tsKvEntry.getKey()) - .setLong(3, partition) - .setLong(4, tsKvEntry.getTs()); - stmtBuilder.setToNull(getColumnName(type)); - if (ttl > 0) { - stmtBuilder.setInt(6, (int) ttl); - } - BoundStatement stmt = stmtBuilder.build(); - return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); - } - private ListenableFuture doSavePartition(TenantId tenantId, EntityId entityId, String key, long ttl, long partition) { log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityId.getEntityType(), entityId.getId(), key); PreparedStatement preparedStatement = ttl == 0 ? getPartitionInsertStmt() : getPartitionInsertTtlStmt(); @@ -591,6 +561,56 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD return deleteStmt; } + private PreparedStatement getSaveWithNullStmt() { + if (saveWithNullStmt == null) { + stmtCreationLock.lock(); + try { + if (saveWithNullStmt == null) { + saveWithNullStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + + "(" + ModelConstants.ENTITY_TYPE_COLUMN + + "," + ModelConstants.ENTITY_ID_COLUMN + + "," + ModelConstants.KEY_COLUMN + + "," + ModelConstants.PARTITION_COLUMN + + "," + ModelConstants.TS_COLUMN + + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + + "," + ModelConstants.STRING_VALUE_COLUMN + + "," + ModelConstants.LONG_VALUE_COLUMN + + "," + ModelConstants.DOUBLE_VALUE_COLUMN + + "," + ModelConstants.JSON_VALUE_COLUMN + ")" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + } + } finally { + stmtCreationLock.unlock(); + } + } + return saveWithNullStmt; + } + + private PreparedStatement getSaveWithNullWithTtlStmt() { + if (saveWithNullWithTtlStmt == null) { + stmtCreationLock.lock(); + try { + if (saveWithNullWithTtlStmt == null) { + saveWithNullWithTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + + "(" + ModelConstants.ENTITY_TYPE_COLUMN + + "," + ModelConstants.ENTITY_ID_COLUMN + + "," + ModelConstants.KEY_COLUMN + + "," + ModelConstants.PARTITION_COLUMN + + "," + ModelConstants.TS_COLUMN + + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + + "," + ModelConstants.STRING_VALUE_COLUMN + + "," + ModelConstants.LONG_VALUE_COLUMN + + "," + ModelConstants.DOUBLE_VALUE_COLUMN + + "," + ModelConstants.JSON_VALUE_COLUMN + ")" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) USING TTL ?"); + } + } finally { + stmtCreationLock.unlock(); + } + } + return saveWithNullWithTtlStmt; + } + private PreparedStatement getSaveStmt(DataType dataType) { if (saveStmts == null) { stmtCreationLock.lock(); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index ad351bb9f2..b06394e8d2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -54,7 +55,9 @@ import java.util.concurrent.TimeoutException; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * @author Andrew Shvayka @@ -64,12 +67,12 @@ import static org.junit.Assert.assertNotNull; public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { @Autowired - TimeseriesService tsService; + protected TimeseriesService tsService; @Autowired EntityViewService entityViewService; - static final int MAX_TIMEOUT = 30; + protected static final int MAX_TIMEOUT = 30; private static final String STRING_KEY = "stringKey"; private static final String LONG_KEY = "longKey"; @@ -84,7 +87,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { KvEntry doubleKvEntry = new DoubleDataEntry(DOUBLE_KEY, Double.MAX_VALUE); KvEntry booleanKvEntry = new BooleanDataEntry(BOOLEAN_KEY, Boolean.TRUE); - private TenantId tenantId; + protected TenantId tenantId; @Before public void before() { @@ -673,6 +676,29 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertEquals(3, list.size()); } + @Test + public void shouldSaveEntryOfEachType() throws Exception { + List timeseries = List.of( + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(1), new BooleanDataEntry("test", true)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(2), new StringDataEntry("test", "text")), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(3), new LongDataEntry("test", 15L)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(4), new DoubleDataEntry("test", 10.5)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(5), new JsonDataEntry("test", "{\"test\":\"testValue\"}"))); + + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + for (TsKvEntry tsKvEntry : timeseries) { + save(tenantId, deviceId, tsKvEntry); + } + + List listUntil3Minutes = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, + TimeUnit.MINUTES.toMillis(3), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(2, listUntil3Minutes.size()); + + List fullList = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, + TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(5, fullList.size()); + } + private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception { TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value)); tsService.save(tenantId, deviceId, entry).get(MAX_TIMEOUT, TimeUnit.SECONDS); @@ -691,6 +717,9 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { return entry; } + private void save(TenantId tenantId, DeviceId deviceId, TsKvEntry tsKvEntry) throws Exception { + tsService.save(tenantId, deviceId, tsKvEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + } private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException, TimeoutException { tsService.save(tenantId, deviceId, toTsEntry(ts, stringKvEntry)).get(MAX_TIMEOUT, TimeUnit.SECONDS); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlSetNullEnabledTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlSetNullEnabledTest.java new file mode 100644 index 0000000000..1bbb37659c --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlSetNullEnabledTest.java @@ -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. + */ +package org.thingsboard.server.dao.service.timeseries.nosql; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.service.DaoNoSqlTest; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@DaoNoSqlTest +@TestPropertySource(properties = { + "cassandra.query.set_null_values_enabled=true", +}) +public class TimeseriesServiceNoSqlSetNullEnabledTest extends TimeseriesServiceNoSqlTest { + + @Override + @Test + public void testNullValuesOfNoneTargetColumn() throws ExecutionException, InterruptedException, TimeoutException { + long ts = TimeUnit.MINUTES.toMillis(1); + TsKvEntry longEntry = new BasicTsKvEntry(ts, new LongDataEntry("temp", 0L)); + double doubleValue = 20.6; + TsKvEntry doubleEntry = new BasicTsKvEntry(ts, new DoubleDataEntry("temp", doubleValue)); + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + tsService.save(tenantId, deviceId, longEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + tsService.save(tenantId, deviceId, doubleEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + + List listWithoutAgg = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, + ts + 1 , 1000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(1, listWithoutAgg.size()); + assertFalse(listWithoutAgg.get(0).getLongValue().isPresent()); + assertTrue(listWithoutAgg.get(0).getDoubleValue().isPresent()); + assertThat(listWithoutAgg.get(0).getDoubleValue().get()).isEqualTo(doubleValue); + + // long value should be set to null after second insert, so avg = doubleValue + List listWithAgg = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, + ts + 1 , 1000, 3, Aggregation.AVG))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(1, listWithAgg.size()); + assertTrue(listWithAgg.get(0).getDoubleValue().isPresent()); + assertThat(listWithAgg.get(0).getDoubleValue().get()).isEqualTo(doubleValue); + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java index 229d5f5842..82916aad1c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java @@ -15,9 +15,105 @@ */ package org.thingsboard.server.dao.service.timeseries.nosql; +import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.junit.Test; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.timeseries.BaseTimeseriesServiceTest; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + @DaoNoSqlTest public class TimeseriesServiceNoSqlTest extends BaseTimeseriesServiceTest { + + @Test + public void shouldSaveEntryOfEachTypeWithTtl() throws ExecutionException, InterruptedException, TimeoutException { + long ttlInSec = TimeUnit.SECONDS.toSeconds(3); + List timeseries = List.of( + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(1), new BooleanDataEntry("test", true)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(2), new StringDataEntry("test", "text")), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(3), new LongDataEntry("test", 15L)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(4), new DoubleDataEntry("test", 10.5)), + new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(5), new JsonDataEntry("test", "{\"test\":\"testValue\"}"))); + + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + tsService.save(tenantId, deviceId, timeseries, ttlInSec); + + List fullList = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, + TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(5, fullList.size()); + + // check entries after ttl + Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSec)); + List listAfterTtl = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, + TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(0, listAfterTtl.size()); + } + + @Test + public void shouldDeleteValueAfterTtl() throws ExecutionException, InterruptedException, TimeoutException { + long longEntryTs = TimeUnit.MINUTES.toMillis(2); + long doubleEntryTs = TimeUnit.MINUTES.toMillis(3); + long ttlInSec = TimeUnit.SECONDS.toSeconds(3); + long longValue = 0L; + TsKvEntry longEntry = new BasicTsKvEntry(longEntryTs, new LongDataEntry("temp", longValue)); + double doubleValue = 20.6; + TsKvEntry doubleEntry = new BasicTsKvEntry(doubleEntryTs, new DoubleDataEntry("temp", doubleValue)); + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + tsService.save(tenantId, deviceId, longEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + tsService.save(tenantId, deviceId, Collections.singletonList(doubleEntry), ttlInSec).get(MAX_TIMEOUT, TimeUnit.SECONDS); + + Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSec)); + List listAfterTtl = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, + doubleEntryTs + 1 , 1000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(1, listAfterTtl.size()); + assertTrue(listAfterTtl.get(0).getLongValue().isPresent()); + assertThat(listAfterTtl.get(0).getLongValue().get()).isEqualTo(longValue); + assertFalse(listAfterTtl.get(0).getDoubleValue().isPresent()); + } + + @Test + public void testNullValuesOfNoneTargetColumn() throws ExecutionException, InterruptedException, TimeoutException { + long ts = TimeUnit.MINUTES.toMillis(1); + long longValue = 10L; + TsKvEntry longEntry = new BasicTsKvEntry(ts, new LongDataEntry("temp", longValue)); + double doubleValue = 20.6; + TsKvEntry doubleEntry = new BasicTsKvEntry(ts, new DoubleDataEntry("temp", doubleValue)); + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + tsService.save(tenantId, deviceId, longEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + tsService.save(tenantId, deviceId, doubleEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); + + List listWithoutAgg = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, + ts + 1 , 1000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(1, listWithoutAgg.size()); + assertTrue(listWithoutAgg.get(0).getLongValue().isPresent()); + assertFalse(listWithoutAgg.get(0).getDoubleValue().isPresent()); + assertThat(listWithoutAgg.get(0).getLongValue().get()).isEqualTo(longValue); + + // long value should not be reset to null, so avg = (doubleValue + longValue)/ 2 + List listWithAgg = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, + ts + 1, 200000, 3, Aggregation.AVG))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(1, listWithAgg.size()); + assertTrue(listWithAgg.get(0).getDoubleValue().isPresent()); + double expectedValue = (doubleValue + longValue)/ 2; + assertThat(listWithAgg.get(0).getDoubleValue().get()).isEqualTo(expectedValue); + } } From 100cdd5cf18f12859940db9284c38f17829a0473 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 2 Feb 2024 14:00:30 +0100 Subject: [PATCH 083/137] refactored due to comments --- .../AbstractTbRuleEngineSubmitStrategy.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java index 6f9a4c49c6..bfec9f747f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractTbRuleEngineSubmitStrategy.java @@ -52,14 +52,12 @@ public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngine List> newOrderedMsgList = new ArrayList<>(reprocessMap.size()); for (IdMsgPair pair : orderedMsgList) { if (reprocessMap.containsKey(pair.uuid)) { - var oldValue = pair.getMsg().getValue(); - if (StringUtils.isNotEmpty(oldValue.getFailureMessage())) { - var newValue = TransportProtos.ToRuleEngineMsg.newBuilder() - .setTenantIdMSB(oldValue.getTenantIdMSB()) - .setTenantIdLSB(oldValue.getTenantIdLSB()) - .setTbMsg(oldValue.getTbMsg()) + if (StringUtils.isNotEmpty(pair.getMsg().getValue().getFailureMessage())) { + var toRuleEngineMsg = TransportProtos.ToRuleEngineMsg.newBuilder(pair.getMsg().getValue()) + .clearFailureMessage() + .clearRelationTypes() .build(); - var newMsg = new TbProtoQueueMsg<>(pair.getMsg().getKey(), newValue, pair.getMsg().getHeaders()); + var newMsg = new TbProtoQueueMsg<>(pair.getMsg().getKey(), toRuleEngineMsg, pair.getMsg().getHeaders()); newOrderedMsgList.add(new IdMsgPair<>(pair.getUuid(), newMsg)); } else { newOrderedMsgList.add(pair); From bb933a4d4e0c76d054792e20eae421ed8a65a1e0 Mon Sep 17 00:00:00 2001 From: kalytka Date: Fri, 2 Feb 2024 15:07:15 +0200 Subject: [PATCH 084/137] Changed share modules logic --- ui-ngx/src/app/modules/common/modules-map.ts | 8 ++++++++ .../home/components/home-components.module.ts | 15 ++------------- .../src/app/modules/home/components/public-api.ts | 4 ++++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index b19a588d8b..84676b9661 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -248,6 +248,10 @@ import * as ImportDialogCsvComponent from '@shared/import-export/import-dialog-c import * as TableColumnsAssignmentComponent from '@shared/import-export/table-columns-assignment.component'; import * as EventContentDialogComponent from '@home/components/event/event-content-dialog.component'; import * as SharedHomeComponentsModule from '@home/components/shared-home-components.module'; +import * as WidgetConfigComponentsModule from '@home/components/widget/config/widget-config-components.module'; +import * as BasicWidgetConfigModule from '@home/components/widget/config/basic/basic-widget-config.module'; +import * as WidgetSettingsCommonModule from '@home/components/widget/lib/settings/common/widget-settings-common.module'; +import * as WidgetComponentsModule from '@home/components/widget/widget-components.module'; import * as SelectTargetLayoutDialogComponent from '@home/components/dashboard/select-target-layout-dialog.component'; import * as SelectTargetStateDialogComponent from '@home/components/dashboard/select-target-state-dialog.component'; import * as AliasesEntityAutocompleteComponent from '@home/components/alias/aliases-entity-autocomplete.component'; @@ -571,6 +575,10 @@ class ModulesMap implements IModulesMap { '@home/components/attribute/add-widget-to-dashboard-dialog.component': AddWidgetToDashboardDialogComponent, '@home/components/event/event-content-dialog.component': EventContentDialogComponent, '@home/components/shared-home-components.module': SharedHomeComponentsModule, + '@home/components/widget/config/widget-config-components.module': WidgetConfigComponentsModule, + '@home/components/widget/config/basic/basic-widget-config.module': BasicWidgetConfigModule, + '@home/components/widget/lib/settings/common/widget-settings-common.module': WidgetSettingsCommonModule, + '@home/components/widget/widget-components.module': WidgetComponentsModule, '@home/components/dashboard/select-target-layout-dialog.component': SelectTargetLayoutDialogComponent, '@home/components/dashboard/select-target-state-dialog.component': SelectTargetStateDialogComponent, '@home/components/alias/aliases-entity-autocomplete.component': AliasesEntityAutocompleteComponent, diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 5d1b2d12f0..6411485f14 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -172,10 +172,7 @@ import { ManageWidgetActionsDialogComponent } from '@home/components/widget/action/manage-widget-actions-dialog.component'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; -import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; -import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module'; -import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; @NgModule({ declarations: @@ -327,16 +324,9 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component SnmpDeviceProfileTransportModule, StatesControllerModule, DeviceCredentialsModule, - DeviceProfileCommonModule, - WidgetSettingsCommonModule, - WidgetComponentsModule, - BasicWidgetConfigModule + DeviceProfileCommonModule ], exports: [ - WidgetComponentsModule, - SharedHomeComponentsModule, - WidgetSettingsCommonModule, - WidgetConfigComponentsModule, RouterTabsComponent, EntitiesTableComponent, AddEntityDialogComponent, @@ -456,8 +446,7 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component RateLimitsComponent, RateLimitsTextComponent, RateLimitsDetailsDialogComponent, - SendNotificationButtonComponent, - BasicWidgetConfigModule + SendNotificationButtonComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/public-api.ts b/ui-ngx/src/app/modules/home/components/public-api.ts index 22748a828c..e5e1fd068d 100644 --- a/ui-ngx/src/app/modules/home/components/public-api.ts +++ b/ui-ngx/src/app/modules/home/components/public-api.ts @@ -15,6 +15,10 @@ /// export * from './home-components.module'; +export * from './widget/config/basic/basic-widget-config.module'; +export * from './widget/lib/settings/common/widget-settings-common.module'; +export * from './widget/widget-components.module'; +export * from './widget/config/widget-config-components.module'; export * from './widget/config/widget-config.component.models'; export * from './widget/lib/table-widget.models'; From 8ce6457ce525713dfe921cbc38f51c355b84fe00 Mon Sep 17 00:00:00 2001 From: kalytka Date: Fri, 2 Feb 2024 15:15:40 +0200 Subject: [PATCH 085/137] Refactoring --- ui-ngx/src/app/modules/common/modules-map.ts | 2 +- .../src/app/modules/home/components/home-components.module.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 84676b9661..50714ec2c3 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -251,7 +251,7 @@ import * as SharedHomeComponentsModule from '@home/components/shared-home-compon import * as WidgetConfigComponentsModule from '@home/components/widget/config/widget-config-components.module'; import * as BasicWidgetConfigModule from '@home/components/widget/config/basic/basic-widget-config.module'; import * as WidgetSettingsCommonModule from '@home/components/widget/lib/settings/common/widget-settings-common.module'; -import * as WidgetComponentsModule from '@home/components/widget/widget-components.module'; +import * as WidgetComponentsModule from '@home/components/widget/widget-components.module'; import * as SelectTargetLayoutDialogComponent from '@home/components/dashboard/select-target-layout-dialog.component'; import * as SelectTargetStateDialogComponent from '@home/components/dashboard/select-target-state-dialog.component'; import * as AliasesEntityAutocompleteComponent from '@home/components/alias/aliases-entity-autocomplete.component'; diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 6411485f14..e8374f6534 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -172,6 +172,7 @@ import { ManageWidgetActionsDialogComponent } from '@home/components/widget/action/manage-widget-actions-dialog.component'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; +import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; @NgModule({ @@ -320,6 +321,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet SharedModule, SharedHomeComponentsModule, WidgetConfigComponentsModule, + BasicWidgetConfigModule, Lwm2mProfileComponentsModule, SnmpDeviceProfileTransportModule, StatesControllerModule, From e3883413a1ce52ece9b89cb7b20f816b78d12a79 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 26 Jan 2024 15:48:01 +0200 Subject: [PATCH 086/137] Make saveRuleChainMetaData @Transactional --- .../org/thingsboard/server/dao/rule/BaseRuleChainService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 6257bb0420..aaa274db4a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -149,6 +149,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override + @Transactional public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData, Function ruleNodeUpdater) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); From 9fa357735a7a327082e32dd883e79f63220453c5 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 2 Feb 2024 15:55:18 +0200 Subject: [PATCH 087/137] timeseries test improvements --- .../timeseries/BaseTimeseriesServiceTest.java | 15 +++++++----- .../nosql/TimeseriesServiceNoSqlTest.java | 24 +------------------ 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index b06394e8d2..a624a2b6e5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -678,12 +678,12 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { @Test public void shouldSaveEntryOfEachType() throws Exception { - List timeseries = List.of( - new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(1), new BooleanDataEntry("test", true)), - new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(2), new StringDataEntry("test", "text")), - new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(3), new LongDataEntry("test", 15L)), - new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(4), new DoubleDataEntry("test", 10.5)), - new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(5), new JsonDataEntry("test", "{\"test\":\"testValue\"}"))); + BasicTsKvEntry booleanEntry = new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(1), new BooleanDataEntry("test", true)); + BasicTsKvEntry stringEntry = new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(2), new StringDataEntry("test", "text")); + BasicTsKvEntry longEntry = new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(3), new LongDataEntry("test", 15L)); + BasicTsKvEntry doubleEntry = new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(4), new DoubleDataEntry("test", 10.5)); + BasicTsKvEntry jsonEntry = new BasicTsKvEntry(TimeUnit.MINUTES.toMillis(5), new JsonDataEntry("test", "{\"test\":\"testValue\"}")); + List timeseries = List.of(booleanEntry, stringEntry, longEntry, doubleEntry, jsonEntry); DeviceId deviceId = new DeviceId(Uuids.timeBased()); for (TsKvEntry tsKvEntry : timeseries) { @@ -693,10 +693,13 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { List listUntil3Minutes = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, TimeUnit.MINUTES.toMillis(3), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); assertEquals(2, listUntil3Minutes.size()); + assertThat(listUntil3Minutes).containsOnlyOnceElementsOf(List.of( + booleanEntry, stringEntry)); List fullList = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); assertEquals(5, fullList.size()); + assertThat(fullList).containsOnlyOnceElementsOf(timeseries); } private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java index 82916aad1c..b8970feca3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceNoSqlTest.java @@ -62,34 +62,12 @@ public class TimeseriesServiceNoSqlTest extends BaseTimeseriesServiceTest { assertEquals(5, fullList.size()); // check entries after ttl - Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSec)); + Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSec + 1)); List listAfterTtl = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("test", 0L, TimeUnit.MINUTES.toMillis(6), 1000, 10, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); assertEquals(0, listAfterTtl.size()); } - @Test - public void shouldDeleteValueAfterTtl() throws ExecutionException, InterruptedException, TimeoutException { - long longEntryTs = TimeUnit.MINUTES.toMillis(2); - long doubleEntryTs = TimeUnit.MINUTES.toMillis(3); - long ttlInSec = TimeUnit.SECONDS.toSeconds(3); - long longValue = 0L; - TsKvEntry longEntry = new BasicTsKvEntry(longEntryTs, new LongDataEntry("temp", longValue)); - double doubleValue = 20.6; - TsKvEntry doubleEntry = new BasicTsKvEntry(doubleEntryTs, new DoubleDataEntry("temp", doubleValue)); - DeviceId deviceId = new DeviceId(Uuids.timeBased()); - tsService.save(tenantId, deviceId, longEntry).get(MAX_TIMEOUT, TimeUnit.SECONDS); - tsService.save(tenantId, deviceId, Collections.singletonList(doubleEntry), ttlInSec).get(MAX_TIMEOUT, TimeUnit.SECONDS); - - Thread.sleep(TimeUnit.SECONDS.toMillis(ttlInSec)); - List listAfterTtl = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery("temp", 0L, - doubleEntryTs + 1 , 1000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); - assertEquals(1, listAfterTtl.size()); - assertTrue(listAfterTtl.get(0).getLongValue().isPresent()); - assertThat(listAfterTtl.get(0).getLongValue().get()).isEqualTo(longValue); - assertFalse(listAfterTtl.get(0).getDoubleValue().isPresent()); - } - @Test public void testNullValuesOfNoneTargetColumn() throws ExecutionException, InterruptedException, TimeoutException { long ts = TimeUnit.MINUTES.toMillis(1); From c05d543b0a02c2c6103cbde70a7738c3d2506b44 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 2 Feb 2024 16:03:35 +0200 Subject: [PATCH 088/137] Add test to ensure message timestamp is used as event timestamp when metadata ts property is not present --- .../thingsboard/server/common/msg/TbMsg.java | 5 ++++ .../engine/action/TbDeviceStateNodeTest.java | 25 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index d81393cf25..cbf9ae34b3 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -131,6 +131,11 @@ public final class TbMsg implements Serializable { metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } + public static TbMsg newMsg(TbMsgType type, EntityId originator, TbMsgMetaData metaData, String data, long ts) { + return new TbMsg(null, UUID.randomUUID(), ts, type, originator, null, + metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); + } + // REALLY NEW MSG /** diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 1ce49b3a7b..d66babea9a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -55,6 +55,7 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; @@ -75,7 +76,7 @@ public class TbDeviceStateNodeTest { private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); - private static final long METADATA_TS = System.currentTimeMillis(); + private static final long METADATA_TS = 123L; private TbMsg cleanupMsg; private TbMsg msg; private long nowNanos; @@ -266,6 +267,28 @@ public class TbDeviceStateNodeTest { then(ctxMock).shouldHaveNoMoreInteractions(); } + @Test + public void givenMetadataDoesNotContainTs_whenOnMsg_thenMsgTsIsUsedAsEventTs() { + // GIVEN + try { + initNode(TbMsgType.ACTIVITY_EVENT); + } catch (TbNodeException e) { + fail("Node failed to initialize!", e); + } + + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); + + long msgTs = METADATA_TS + 1; + msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_OBJECT, msgTs); + + // WHEN + node.onMsg(ctxMock, msg); + + // THEN + then(deviceStateManagerMock).should().onDeviceActivity(eq(TENANT_ID), eq(DEVICE_ID), eq(msgTs), any()); + } + @ParameterizedTest @MethodSource public void givenSupportedEventAndDeviceOriginator_whenOnMsg_thenCorrectEventIsSentWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { From afed69e7b526198210da65ed25535d4f4a845370 Mon Sep 17 00:00:00 2001 From: rusikv Date: Fri, 2 Feb 2024 18:38:30 +0200 Subject: [PATCH 089/137] UI: fixed dashboard entity filter remebers user input on close and cancel --- .../modules/home/components/filter/filters-edit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts b/ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts index 080fd3e576..a37ae90dc0 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts @@ -119,7 +119,7 @@ export class FiltersEditComponent implements OnInit, OnDestroy { const filteredArray = Object.entries(this.filtersInfo); if (filteredArray.length === 1) { - const singleFilter: Filter = {id: filteredArray[0][0], ...filteredArray[0][1]}; + const singleFilter: Filter = {id: filteredArray[0][0], ...deepClone(filteredArray[0][1])}; this.dialog.open(UserFilterDialogComponent, { disableClose: true, From 8cdde9e67c3523b48436aba4466caae0c95f0da9 Mon Sep 17 00:00:00 2001 From: artem Date: Sat, 3 Feb 2024 21:59:33 +0200 Subject: [PATCH 090/137] TbGpsGeofencingActionNodeTest: added extends AbstractRuleNodeUpgradeTest added @ExtendWith(MockitoExtension.class) and refactored --- .../geo/TbGpsGeofencingActionNodeTest.java | 105 ++++++++---------- 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java index d61688d5a9..fe78f57f4e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -15,40 +15,37 @@ */ package org.thingsboard.rule.engine.geo; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.Futures; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +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.MethodSource; -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.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; -import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; import java.time.Duration; -import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.ENTERED; @@ -57,16 +54,17 @@ import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.LEFT; import static org.thingsboard.rule.engine.util.GpsGeofencingEvents.OUTSIDE; import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCESS; -class TbGpsGeofencingActionNodeTest { +@ExtendWith(MockitoExtension.class) +class TbGpsGeofencingActionNodeTest extends AbstractRuleNodeUpgradeTest { + @Mock private TbContext ctx; - private TbGpsGeofencingActionNode node; + @Mock private AttributesService attributesService; + private TbGpsGeofencingActionNode node; @BeforeEach void setUp() { - ctx = mock(TbContext.class); - attributesService = mock(AttributesService.class); - node = new TbGpsGeofencingActionNode(); + node = spy(new TbGpsGeofencingActionNode()); } @AfterEach @@ -80,23 +78,37 @@ class TbGpsGeofencingActionNodeTest { long tsNowMinusMinuteAndMillis = tsNow - Duration.ofMinutes(1).plusMillis(1).toMillis(); return Stream.of( // default config with presenceMonitoringStrategyOnEachMessage false and msgInside true - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(false, 0, false)), ENTERED), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNow, false)), SUCCESS), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, new EntityGeofencingState(true, tsNow, true)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, false, + new EntityGeofencingState(true, tsNow, true)), SUCCESS), // default config with presenceMonitoringStrategyOnEachMessage false and msgInside false - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, 0, false)), LEFT), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNow, false)), SUCCESS), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, new EntityGeofencingState(false, tsNow, true)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNow, false)), SUCCESS), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, false, + new EntityGeofencingState(false, tsNow, true)), SUCCESS), // default config with presenceMonitoringStrategyOnEachMessage true and msgInside true - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(false, 0, false)), ENTERED), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(true, tsNow, false)), INSIDE), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(false, 0, false)), ENTERED), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(true, tsNow, false)), INSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, true, true, + new EntityGeofencingState(true, tsNowMinusMinuteAndMillis, false)), INSIDE), // default config with presenceMonitoringStrategyOnEachMessage true and msgInside false - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, 0, false)), LEFT), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, tsNow, false)), OUTSIDE), - Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE) + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, 0, false)), LEFT), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, tsNow, false)), OUTSIDE), + Arguments.of(new GpsGeofencingActionTestCase(deviceId, false, true, + new EntityGeofencingState(false, tsNowMinusMinuteAndMillis, false)), OUTSIDE) ); } @@ -117,10 +129,6 @@ class TbGpsGeofencingActionNodeTest { getOutsideRectangleTbMsg(gpsGeofencingActionTestCase.getEntityId()); when(ctx.getAttributesService()).thenReturn(attributesService); - when(ctx - .getAttributesService() - .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getServiceId())) - .thenReturn(Futures.immediateFuture(Optional.empty())); ReflectionTestUtils.setField(node, "entityStates", gpsGeofencingActionTestCase.getEntityStates()); @@ -128,24 +136,16 @@ class TbGpsGeofencingActionNodeTest { node.onMsg(ctx, msg); // THEN + verify(ctx.getAttributesService(), never()).find(any(), any(), any(), anyString()); verify(ctx, never()).tellFailure(any(), any(Throwable.class)); verify(ctx, never()).enqueueForTellNext(any(), eq(expectedOutput), any(), any()); verify(ctx, never()).ack(any()); - ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); if (SUCCESS.equals(expectedOutput)) { - verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); + verify(ctx).tellSuccess(eq(msg)); } else { - verify(ctx, times(1)).tellNext(newMsgCaptor.capture(), eq(expectedOutput)); + verify(ctx).tellNext(eq(msg), eq(expectedOutput)); } - - var actualMsg = newMsgCaptor.getValue(); - assertThat(actualMsg).isNotNull(); - assertThat(actualMsg).isSameAs(msg); - assertThat(actualMsg.getType()).isSameAs(msg.getType()); - assertThat(actualMsg.getData()).isSameAs(msg.getData()); - assertThat(actualMsg.getMetaData()).isEqualTo(msg.getMetaData()); - assertThat(actualMsg.getOriginator()).isSameAs(msg.getOriginator()); } private TbMsg getOutsideRectangleTbMsg(EntityId entityId) { @@ -250,20 +250,9 @@ class TbGpsGeofencingActionNodeTest { ); } - @ParameterizedTest - @MethodSource - void givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig(int givenVersion, String givenConfigStr, boolean hasChanges, String expectedConfigStr) throws TbNodeException { - // GIVEN - JsonNode givenConfig = JacksonUtil.toJsonNode(givenConfigStr); - JsonNode expectedConfig = JacksonUtil.toJsonNode(expectedConfigStr); - - // WHEN - TbPair upgradeResult = node.upgrade(givenVersion, givenConfig); - - // THEN - assertThat(upgradeResult.getFirst()).isEqualTo(hasChanges); - ObjectNode upgradedConfig = (ObjectNode) upgradeResult.getSecond(); - assertThat(upgradedConfig).isEqualTo(expectedConfig); + @Override + protected TbNode getTestNode() { + return node; } } From b0b8e56c97d63f2dcd6fc8b00706ef3ba0d3f312 Mon Sep 17 00:00:00 2001 From: artem Date: Sat, 3 Feb 2024 22:04:20 +0200 Subject: [PATCH 091/137] small changes --- .../thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java | 1 + .../rule/engine/geo/TbGpsGeofencingActionNodeTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java index 8444c9cf0d..804d30d28f 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/GpsGeofencingActionTestCase.java @@ -23,6 +23,7 @@ import java.util.Map; @Data public class GpsGeofencingActionTestCase { + private EntityId entityId; private Map entityStates; private boolean msgInside; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java index fe78f57f4e..5cd7e02c59 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGpsGeofencingActionNodeTest.java @@ -56,6 +56,7 @@ import static org.thingsboard.server.common.data.msg.TbNodeConnectionType.SUCCES @ExtendWith(MockitoExtension.class) class TbGpsGeofencingActionNodeTest extends AbstractRuleNodeUpgradeTest { + @Mock private TbContext ctx; @Mock From f7914a4f96e3d0dd183b84ea147d5774678aa731 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 5 Feb 2024 11:43:11 +0200 Subject: [PATCH 092/137] UI: Clear translate zh_CN - removed keys that do not translate --- .../assets/locale/locale.constant-zh_CN.json | 446 +----------------- 1 file changed, 1 insertion(+), 445 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 10db674ea3..22c8073c37 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -4276,426 +4276,6 @@ "displayTypePrefix": "显示实时/历史前缀", "preview": "预览" }, - "unit": { - "millimeter": "毫米", - "centimeter": "厘米", - "angstrom": "埃", - "nanometer": "纳米", - "micrometer": "微米", - "meter": "米", - "kilometer": "千米", - "inch": "英寸", - "foot": "英尺", - "yard": "码", - "mile": "英里", - "nautical-mile": "海里", - "astronomical-unit": "天文单位", - "reciprocal-metre": "倒数米", - "meter-per-meter": "米/米", - "steradian": "球面度", - "thou": "千分之一英寸", - "barleycorn": "麦粒", - "hand": "手", - "chain": "链", - "furlong": "英寻", - "league": "里", - "fathom": "英寻", - "cable": "缆", - "link": "连", - "rod": "竿", - "nanogram": "纳克", - "microgram": "微克", - "milligram": "毫克", - "gram": "克", - "kilogram": "千克", - "tonne": "公吨", - "ounce": "盎司", - "pound": "磅", - "stone": "英石", - "hundredweight-count": "百磅", - "short-tons": "短吨", - "dalton": "道尔顿", - "grain": "格令", - "drachm": "打兰", - "quarter": "夸特", - "slug": "斯勒格", - "carat": "克拉", - "cubic-millimeter": "立方毫米", - "cubic-centimeter": "立方厘米", - "cubic-meter": "立方米", - "cubic-kilometer": "立方千米", - "microliter": "微升", - "milliliter": "毫升", - "liter": "升", - "hectoliter": "公石", - "cubic-inch": "立方英寸", - "cubic-foot": "立方英尺", - "cubic-yard": "立方码", - "fluid-ounce": "液盎司", - "pint": "品脱", - "quart": "夸脱", - "gallon": "加仑", - "oil-barrels": "桶", - "cubic-meter-per-kilogram": "立方米/千克", - "gill": "吉尔", - "hogshead": "大桶", - "teaspoon": "茶匙", - "tablespoon": "汤匙", - "cup": "杯", - "celsius": "摄氏度", - "kelvin": "开尔文", - "rankine": "兰氏度", - "fahrenheit": "华氏度", - "percent": "百分比", - "meter-per-second": "米/秒", - "kilometer-per-hour": "千米/时", - "foot-per-second": "英尺/秒", - "mile-per-hour": "英里/时", - "knot": "节", - "millimeters-per-minute": "毫米/分钟", - "kilometer-per-hour-squared": "千米/二次方时", - "foot-per-second-squared": "英尺/二次方秒", - "pascal": "帕斯卡", - "kilopascal": "千帕", - "megapascal": "兆帕", - "gigapascal": "吉帕", - "millibar": "毫巴", - "bar": "巴", - "kilobar": "千巴", - "newton": "牛顿", - "newton-meter": "牛顿米", - "foot-pounds": "英尺磅", - "inch-pounds": "英寸磅", - "newton-per-meter": "牛顿/米", - "atmospheres": "大气压", - "pounds-per-square-inch": "磅/平方英寸", - "torr": "托", - "inches-of-mercury": "英寸汞柱", - "pascal-per-square-meter": "帕斯卡/平方米", - "pound-per-square-inch": "磅/平方英寸", - "newton-per-square-meter": "牛顿/平方米", - "kilogram-force-per-square-meter": "千克力/平方米", - "pascal-per-square-centimeter": "帕斯卡/平方厘米", - "ton-force-per-square-inch": "吨力/平方英寸", - "kilonewton-per-square-meter": "千牛/平方米", - "newton-per-square-millimeter": "牛顿/平方毫米", - "microjoule": "微焦", - "millijoule": "毫焦", - "joule": "焦", - "kilojoule": "千焦", - "megajoule": "兆焦", - "gigajoule": "吉焦", - "watt-hour": "瓦时", - "kilowatt-hour": "千瓦时", - "electron-volts": "电子伏", - "joules-per-coulomb": "焦耳/库仑", - "british-thermal-unit": "英国热单位", - "foot-pound": "英尺磅", - "calorie": "卡路里", - "small-calorie": "小卡", - "kilocalorie": "千卡", - "joule-per-kelvin": "焦耳/开", - "joule-per-kilogram-kelvin": "焦耳/千克·开", - "joule-per-kilogram": "焦耳/千克", - "watt-per-meter-kelvin": "瓦/米·开", - "joule-per-cubic-meter": "焦耳/立方米", - "therm": "热", - "electric-dipole-moment": "电偶极矩", - "magnetic-dipole-moment": "磁偶极矩", - "debye": "戴拜", - "coulomb-per-square-meter-per-volt": "库仑·平方米/伏", - "milliwatt": "毫瓦", - "microwatt": "微瓦", - "watt": "瓦", - "kilowatt": "千瓦", - "megawatt": "兆瓦", - "gigawatt": "吉瓦", - "metric-horsepower": "公制马力", - "milliwatt-per-square-centimeter": "毫瓦/平方厘米", - "watt-per-square-centimeter": "瓦/平方厘米", - "kilowatt-per-square-centimeter": "千瓦/平方厘米", - "milliwatt-per-square-meter": "毫瓦/平方米", - "watt-per-square-meter": "瓦/平方米", - "kilowatt-per-square-meter": "千瓦/平方米", - "watt-per-square-inch": "瓦/平方英寸", - "kilowatt-per-square-inch": "千瓦/平方英寸", - "horsepower": "马力", - "btu-per-hour": "英国热单位/时", - "coulomb": "库仑", - "millicoulomb": "毫库仑", - "microcoulomb": "微库仑", - "picocoulomb": "皮库仑", - "coulomb-per-meter": "库仑/米", - "coulomb-per-cubic-meter": "库仑/立方米", - "coulomb-per-square-meter": "库仑/平方米", - "square-millimeter": "平方毫米", - "square-centimeter": "平方厘米", - "square-meter": "平方米", - "hectare": "公顷", - "square-kilometer": "平方千米", - "square-inch": "平方英寸", - "square-foot": "平方英尺", - "square-yard": "平方码", - "acre": "英亩", - "square-mile": "平方英里", - "are": "公亩", - "barn": "bar", - "circular-inch": "圆英寸", - "milliampere-hour": "毫安时", - "milliampere-hour-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 毫安时, 毫安时, mAh", - "ampere-hours": "安时", - "ampere-hours-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 安培, 安时, Ah", - "kiloampere-hours": "千安时", - "kiloampere-hours-tags": "电流, 电流流动, 电荷, 电流容量, 电流, 电气流, 千安时, 千安时, kAh", - "nanoampere": "纳安培", - "nanoampere-tags": "电流, 安培, 纳安培, nA", - "picoampere": "皮安培", - "picoampere-tags": "电流, 安培, 皮安培, pA", - "microampere": "微安培", - "microampere-tags": "电流, 微安培, 微安培, μA", - "milliampere": "毫安培", - "milliampere-tags": "电流, 毫安培, 毫安培, mA", - "ampere": "安培", - "ampere-tags": "电流, 电流流动, 电流, 电气流, 安培, 安培, 电流, A", - "kiloamperes": "千安培", - "kiloamperes-tags": "电流, 电流流动, 千安培, kA", - "microampere-per-square-centimeter": "微安培/平方厘米", - "microampere-per-square-centimeter-tags": "电流密度, 每平方厘米的微安培, µA/cm²", - "ampere-per-square-meter": "安培/平方米", - "ampere-per-square-meter-tags": "电流密度, 单位面积电流, 每平方米的安培, A/m²", - "ampere-per-meter": "安培/米", - "ampere-per-meter-tags": "磁场强度, 磁场强度, 每米的安培, A/m", - "oersted": "厄斯特德", - "oersted-tags": "磁场, 厄斯特德, Oe", - "bohr-magneton": "玻尔磁子", - "bohr-magneton-tags": "原子物理, 磁矩, 玻尔磁子, μB", - "ampere-meter-squared": "安培·平方米", - "ampere-meter-squared-tags": "磁矩, 偶极矩, 安培·平方米, A·m²", - "ampere-meter": "安培·米", - "ampere-meter-tags": "磁场, 电流环, 安培·米, A·m", - "nanovolt": "纳伏", - "picovolt": "皮伏", - "millivolts": "毫伏", - "microvolts": "微伏", - "volt": "伏", - "kilovolts": "千伏", - "dbmV": "分贝毫伏", - "dbm": "分贝毫瓦", - "volt-meter": "伏·米", - "kilovolt-meter": "千伏·米", - "megavolt-meter": "兆伏·米", - "microvolt-meter": "微伏·米", - "millivolt-meter": "毫伏·米", - "nanovolt-meter": "纳伏·米", - "ohm": "欧姆", - "microohm": "微欧", - "milliohm": "毫欧", - "kilohm": "千欧", - "megohm": "兆欧", - "gigohm": "吉欧", - "hertz": "赫兹", - "kilohertz": "千赫", - "megahertz": "兆赫", - "gigahertz": "吉赫", - "rpm": "转数/分钟", - "candela-per-square-meter": "坎德拉/平方米", - "candela": "坎德拉", - "lumen": "流明", - "lux": "勒克斯", - "foot-candle": "英尺烛光", - "lumen-per-square-meter": "流明/平方米", - "lux-second": "勒克斯·秒", - "lumen-second": "流明·秒", - "lumens-per-watt": "流明/瓦", - "absorbance": "吸光度", - "mole": "摩尔", - "nanomole": "纳摩尔", - "micromole": "微摩尔", - "millimole": "毫摩尔", - "kilomole": "千摩尔", - "mole-per-cubic-meter": "摩尔/立方米", - "rssi": "接收信号强度指示", - "ppm": "百万分之一", - "ppb": "十亿分之一", - "micrograms-per-cubic-meter": "微克/立方米", - "aqi": "空气质量指数", - "gram-per-cubic-meter": "克/立方米", - "gram-per-kilogram": "比湿", - "millimeters-per-second": "毫米/秒", - "neper": "纳珀", - "bel": "贝尔", - "decibel": "分贝", - "meters-per-second-squared": "平方米/秒", - "becquerel": "贝克勒", - "curie": "居里", - "gray": "戈瑞", - "sievert": "西弗特", - "roentgen": "伦琴", - "cps": "计数/秒", - "rad": "拉德", - "rem": "剂量当量", - "dps": "衰变/秒", - "rutherford": "卢瑟福", - "coulombs-per-kilogram": "库仑/千克", - "becquerels-per-cubic-meter": "贝克勒/立方米", - "curies-per-liter": "居里/升", - "becquerels-per-second": "贝克勒/秒", - "curies-per-second": "居里/秒", - "gy-per-second": "戈瑞/秒", - "watt-per-steradian": "瓦/立体弧度", - "watt-per-square-metre-steradian": "瓦/平方米·弧度", - "ph-level": "酸碱度", - "turbidity": "浊度", - "mg-per-liter": "毫克/升", - "microsiemens-per-centimeter": "微西门子/厘米", - "millisiemens-per-meter": "毫西门子/米", - "siemens-per-meter": "西门子/米", - "kilogram-per-cubic-meter": "千克/立方米", - "gram-per-cubic-centimeter": "克/立方厘米", - "kilogram-per-square-meter": "千克/平方米", - "milligram-per-milliliter": "毫克/毫升", - "milligram-per-cubic-meter": "毫克/立方米", - "pound-per-cubic-foot": "磅/立方英尺", - "ounces-per-cubic-inch": "盎司/立方英寸", - "tons-per-cubic-yard": "吨/立方码", - "particle-density": "颗粒密度", - "kilometers-per-liter": "千米/升", - "miles-per-gallon": "英里/加仑", - "liters-per-100-km": "升/100千米", - "gallons-per-mile": "加仑/英里", - "liters-per-hour": "升/小时", - "gallons-per-hour": "加仑/小时", - "beats-per-minute": "每分钟心跳数", - "millimeters-of-mercury": "毫米汞柱", - "milligrams-per-deciliter": "毫克/分升", - "g-force": "重力加速度", - "kilonewton": "千牛", - "kilogram-force": "千克力", - "pound-force": "磅力", - "kilopound-force": "千磅力", - "dyne": "达因", - "poundal": "磅力秒平方英尺", - "kip": "千磅力", - "gal": "加尔", - "gravity": "重力", - "hectopascal": "百帕", - "atmosphere": "大气压", - "millibars": "毫巴", - "inch-of-mercury": "英寸汞柱", - "richter-scale": "里氏震级", - "second": "秒", - "minute": "分钟", - "hour": "小时", - "day": "天", - "week": "周", - "month": "月", - "year": "年", - "cubic-foot-per-minute": "立方英尺/分钟", - "cubic-meters-per-hour": "立方米/小时", - "cubic-meters-per-second": "立方米/秒", - "liter-per-second": "升/秒", - "liter-per-minute": "升/分钟", - "gallons-per-minute": "加仑/分钟", - "cubic-foot-per-second": "立方英尺/秒", - "milliliters-per-minute": "毫升/分钟", - "bit": "位", - "byte": "字节", - "kilobyte": "千字节", - "megabyte": "兆字节", - "gigabyte": "吉字节", - "terabyte": "太字节", - "petabyte": "拍字节", - "exabyte": "艾字节", - "zettabyte": "泽字节", - "yottabyte": "尧字节", - "bit-per-second": "比特/秒", - "kilobit-per-second": "千比特/秒", - "megabit-per-second": "兆比特/秒", - "gigabit-per-second": "吉比特/秒", - "terabit-per-second": "太比特/秒", - "byte-per-second": "字节/秒", - "kilobyte-per-second": "千字节/秒", - "megabyte-per-second": "兆字节/秒", - "gigabyte-per-second": "吉字节/秒", - "degree": "度", - "radian": "弧度", - "gradian": "百分度", - "mil": "米尔", - "revolution": "圈", - "siemens": "西门子", - "millisiemens": "毫西门子", - "microsiemens": "微西门子", - "kilosiemens": "千西门子", - "megasiemens": "兆西门子", - "gigasiemens": "吉西门子", - "farad": "法拉", - "millifarad": "毫法拉", - "microfarad": "微法拉", - "nanofarad": "纳法拉", - "picofarad": "皮法拉", - "kilofarad": "千法拉", - "megafarad": "兆法拉", - "gigafarad": "吉法拉", - "terfarad": "太法拉", - "farad-per-meter": "法拉/米", - "tesla": "特斯拉", - "gauss": "高斯", - "kilogauss": "千高斯", - "millitesla": "毫特斯拉", - "microtesla": "微特斯拉", - "nanotesla": "纳特斯拉", - "kilotesla": "千特斯拉", - "megatesla": "兆特斯拉", - "millitesla-square-meters": "毫特斯拉平方米", - "gamma": "伽马", - "lambda": "兰姆达", - "square-meter-per-second": "平方米/秒", - "square-centimeter-per-second": "平方厘米/秒", - "stoke": "斯托克", - "centistokes": "厘斯托克", - "square-foot-per-second": "平方英尺/秒", - "square-inch-per-second": "平方英寸/秒", - "pascal-second": "帕斯卡秒", - "centipoise": "厘泊", - "poise": "泊", - "reynolds": "雷诺", - "pound-per-foot-hour": "磅/英尺-小时", - "newton-second-per-square-meter": "牛顿秒/平方米", - "dyne-second-per-square-centimeter": "达因秒/平方厘米", - "kilogram-per-meter-second": "千克/米·秒", - "tesla-square-meters": "特斯拉平方米", - "maxwell": "麦克斯韦", - "tesla-per-meter": "特斯拉/米", - "gauss-per-centimeter": "高斯/厘米", - "weber": "韦伯", - "microweber": "微韦伯", - "milliweber": "毫韦伯", - "gauss-square-centimeter": "高斯平方厘米", - "kilogauss-square-centimeter": "千高斯平方厘米", - "henry": "亨利", - "millihenry": "毫亨利", - "microhenry": "微亨利", - "nanohenry": "纳亨利", - "henry-per-meter": "亨利/米", - "tesla-meter-per-ampere": "特斯拉米/安", - "gauss-per-oersted": "高斯/厘斯特", - "kilogram-per-mole": "千克/摩尔", - "gram-per-mole": "克/摩尔", - "milligram-per-mole": "毫克/摩尔", - "joule-per-mole": "焦耳/摩尔", - "joule-per-mole-kelvin": "焦耳/摩尔·开尔文", - "millivolts-per-meter": "毫伏/米", - "volts-per-meter": "伏/米", - "kilovolts-per-meter": "千伏/米", - "radian-per-second": "弧度/秒", - "radian-per-second-squared": "弧度/二次方秒", - "revolutions-per-minute-per-second": "角加速度", - "revolutions-per-minute-per-second-squared": "角加速度", - "deg-per-second": "度/秒", - "degrees-brix": "白利糖度", - "katal": "卡塔尔", - "katal-per-cubic-metre": "卡塔尔/立方米" - }, "user": { "user": "用户", "users": "用户", @@ -6696,30 +6276,6 @@ "items-per-page-separator": "of" }, "language": { - "language": "语言", - "locales": { - "ca_ES": "Catalan", - "cs_CZ": "Česky", - "da_DK": "Dansk", - "de_DE": "Deutsch", - "el_GR": "Ελληνικά", - "en_US": "English", - "es_ES": "Español", - "fa_IR": "فارسي", - "fr_FR": "Français", - "it_IT": "Italiano", - "ja_JP": "日本語", - "ka_GE": "ქართული", - "ko_KR": "한국어", - "lv_LV": "Latviešu", - "nl_BE": "Koninkrijk België", - "pt_BR": "Português do Brasil", - "ro_RO": "Română", - "sl_SI": "Slovenščina", - "tr_TR": "Türkçe", - "uk_UA": "Українська", - "zh_CN": "简体中文", - "zh_TW": "繁體中文" - } + "language": "语言" } } From 567e6a493ee265d42e252abd0905f97e21a9f6dd Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 5 Feb 2024 12:57:22 +0200 Subject: [PATCH 093/137] UI: Refactoring after review --- .../components/vc/repository-settings.component.html | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 10 +++++----- .../home/pages/account/account-routing.module.ts | 1 + .../modules/home/pages/admin/admin-routing.module.ts | 1 + .../home/pages/admin/oauth2-settings.component.ts | 5 ++--- .../home/pages/admin/security-settings.component.ts | 5 ++--- .../pages/admin/two-factor-auth-settings.component.ts | 5 ++--- ui-ngx/src/app/modules/home/pages/home-pages.models.ts | 4 ++-- .../pages/notification/notification-routing.module.ts | 1 - .../home/pages/widget/widget-library-routing.module.ts | 1 - ui-ngx/src/app/shared/components/page.component.ts | 5 ++++- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index 7ba277364d..9b1c627ff2 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -28,7 +28,7 @@ -
    +
    diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index a314803e9c..0b41a90ae3 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -150,14 +150,14 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni this.textSearch.reset('', {emitEvent: false}); this.activeComponent = activeComponent; let showLoadingBar: boolean; - if (isDefined(this.activeComponent.activatedRoute?.data?.value?.showLoadingBar)) { - showLoadingBar = this.activeComponent.activatedRoute?.data?.value?.showLoadingBar; - } else if (isDefined(this.activeComponent?.showLoadingBar)) { - showLoadingBar = this.activeComponent.showLoadingBar; + if (isDefined(this.activeComponent.activatedRoute?.snapshot?.data?.showMainLoadingBar)) { + showLoadingBar = this.activeComponent.activatedRoute.snapshot.data.showMainLoadingBar; + } else if (isDefined(this.activeComponent?.showMainLoadingBar)) { + showLoadingBar = this.activeComponent.showMainLoadingBar; } if (activeComponent && activeComponent instanceof RouterTabsComponent) { this.hideLoadingBar = isDefinedAndNotNull(showLoadingBar) ? !showLoadingBar : true; - } else if (isDefinedAndNotNull(showLoadingBar)) { + } else if (activeComponent && activeComponent instanceof PageComponent && isDefinedAndNotNull(showLoadingBar)) { this.hideLoadingBar = !showLoadingBar; } if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) { diff --git a/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts index 252250ece2..20e6cc73e1 100644 --- a/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/account/account-routing.module.ts @@ -33,6 +33,7 @@ const routes: Routes = [ component: RouterTabsComponent, data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + showMainLoadingBar: false, breadcrumb: { label: 'account.account', icon: 'account_circle' diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 74c5012791..63fb9a14de 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -136,6 +136,7 @@ const routes: Routes = [ component: RouterTabsComponent, data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + showMainLoadingBar: false, breadcrumb: { label: 'admin.settings', icon: 'settings' diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 798b07c461..1d346c70f1 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -54,14 +54,13 @@ import { TranslateService } from '@ngx-translate/core'; import { isDefined, isDefinedAndNotNull, randomAlphanumeric } from '@core/utils'; import { OAuth2Service } from '@core/http/oauth2.service'; import { ActivatedRoute } from '@angular/router'; -import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-oauth2-settings', templateUrl: './oauth2-settings.component.html', styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] }) -export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, HasShowLoading, OnDestroy { +export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { constructor(protected store: Store, private route: ActivatedRoute, @@ -115,7 +114,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha templateProvider = ['Custom']; - showLoadingBar = false; + showMainLoadingBar = false; private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts index f9449eba33..17dda4a0c9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -37,19 +37,18 @@ import { AuthService } from '@core/auth/auth.service'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; -import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-security-settings', templateUrl: './security-settings.component.html', styleUrls: ['./security-settings.component.scss', './settings-card.scss'] }) -export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm, HasShowLoading { +export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm { securitySettingsFormGroup: UntypedFormGroup; jwtSecuritySettingsFormGroup: UntypedFormGroup; - showLoadingBar = false; + showMainLoadingBar = false; private securitySettings: SecuritySettings; private jwtSettings: JwtSettings; diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index 208d133e3e..5355114586 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -32,14 +32,13 @@ import { isNotEmptyStr } from '@core/utils'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { MatExpansionPanel } from '@angular/material/expansion'; -import { HasShowLoading } from '@home/pages/home-pages.models'; @Component({ selector: 'tb-2fa-settings', templateUrl: './two-factor-auth-settings.component.html', styleUrls: [ './settings-card.scss', './two-factor-auth-settings.component.scss'] }) -export class TwoFactorAuthSettingsComponent extends PageComponent implements OnInit, HasConfirmForm, HasShowLoading, OnDestroy { +export class TwoFactorAuthSettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { private readonly destroy$ = new Subject(); private readonly posIntValidation = [Validators.required, Validators.min(1), Validators.pattern(/^\d*$/)]; @@ -48,7 +47,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI twoFactorAuthProviderType = TwoFactorAuthProviderType; twoFactorAuthProvidersData = twoFactorAuthProvidersData; - showLoadingBar = false; + showMainLoadingBar = false; @ViewChildren(MatExpansionPanel) expansionPanel: QueryList; diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts index 456bbb6999..d2b7e9782c 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts @@ -31,7 +31,7 @@ export const entityDetailsPageBreadcrumbLabelFunction: BreadCrumbLabelFunction; loadingSubscription: Subscription; disabledOnLoadFormControls: Array = []; + showMainLoadingBar = true; + protected constructor(protected store: Store) { this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), share()); } From f46aed1b8e1fce42426c6cf10b0c83b7879bcd41 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Mon, 5 Feb 2024 14:38:00 +0200 Subject: [PATCH 094/137] UI: fixed gridster options update for mobile mode --- .../home/components/dashboard/dashboard.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 469630c212..95b03a55ee 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -290,12 +290,12 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); } - if (updateMobileOpts) { - this.updateMobileOpts(); - } if (updateLayoutOpts) { this.updateLayoutOpts(); } + if (updateMobileOpts) { + this.updateMobileOpts(); + } if (updateEditingOpts) { this.updateEditingOpts(); } From c17b2d3b487f66c2ce7b31e9d9a7542e91495300 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 6 Feb 2024 11:23:00 +0200 Subject: [PATCH 095/137] UI: Refactoring --- .../vc/repository-settings.component.html | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 18 ++++++++---------- .../modules/home/pages/home-pages.models.ts | 5 ----- .../app/shared/components/page.component.ts | 3 +-- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index 9b1c627ff2..2fda773889 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -28,7 +28,7 @@ -
    +
    diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 0b41a90ae3..d6c07004c0 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -149,17 +149,15 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni this.hideLoadingBar = false; this.textSearch.reset('', {emitEvent: false}); this.activeComponent = activeComponent; - let showLoadingBar: boolean; - if (isDefined(this.activeComponent.activatedRoute?.snapshot?.data?.showMainLoadingBar)) { - showLoadingBar = this.activeComponent.activatedRoute.snapshot.data.showMainLoadingBar; - } else if (isDefined(this.activeComponent?.showMainLoadingBar)) { - showLoadingBar = this.activeComponent.showMainLoadingBar; - } - if (activeComponent && activeComponent instanceof RouterTabsComponent) { - this.hideLoadingBar = isDefinedAndNotNull(showLoadingBar) ? !showLoadingBar : true; - } else if (activeComponent && activeComponent instanceof PageComponent && isDefinedAndNotNull(showLoadingBar)) { - this.hideLoadingBar = !showLoadingBar; + + if (activeComponent && activeComponent instanceof RouterTabsComponent + && isDefinedAndNotNull(this.activeComponent.activatedRoute?.snapshot?.data?.showMainLoadingBar)) { + this.hideLoadingBar = !this.activeComponent.activatedRoute.snapshot.data.showMainLoadingBar; + } else if (activeComponent && activeComponent instanceof PageComponent + && isDefinedAndNotNull(this.activeComponent?.showMainLoadingBar)) { + this.hideLoadingBar = !this.activeComponent.showMainLoadingBar; } + if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) { this.searchEnabled = true; this.searchableComponent = this.activeComponent; diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts index d2b7e9782c..848bb0b85c 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.models.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.models.ts @@ -30,8 +30,3 @@ export const entityDetailsPageBreadcrumbLabelFunction: BreadCrumbLabelFunction; loadingSubscription: Subscription; From ff6074807ab3b25a571177c3f3c361b01ce188e5 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 6 Feb 2024 13:48:21 +0200 Subject: [PATCH 096/137] SNMP: request chunk sending delay; refactoring --- .../src/main/resources/thingsboard.yml | 7 +- .../transport/snmp/SnmpTransportContext.java | 2 + .../snmp/service/SnmpTransportService.java | 125 +++++--- .../snmp/session/DeviceSessionContext.java | 6 +- .../transport/snmp/session/ScheduledTask.java | 62 ++++ .../transport/snmp/SnmpDeviceSimulatorV2.java | 19 +- .../server/transport/snmp/SnmpTestV2.java | 42 ++- .../snmp-device-profile-transport-config.json | 43 --- .../snmp-device-transport-config-v3.json | 13 - .../snmp-device-transport-config.json | 6 - .../test/resources/snmp_device_profile.json | 289 ++++++++++++++++++ .../service/DefaultTransportService.java | 12 +- .../src/main/resources/tb-snmp-transport.yml | 7 +- 13 files changed, 492 insertions(+), 141 deletions(-) create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/ScheduledTask.java delete mode 100644 common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json delete mode 100644 common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json delete mode 100644 common/transport/snmp/src/test/resources/snmp-device-transport-config.json create mode 100644 common/transport/snmp/src/test/resources/snmp_device_profile.json diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 936f30af60..6a20c3bcfc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1173,14 +1173,17 @@ transport: bind_port: "${SNMP_BIND_PORT:1620}" response_processing: # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices - parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:4}" # to configure SNMP to work over UDP or TCP underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" - # Batch size to request OID mappings from the device (useful when the device profile has multiple hundreds of OID mappings) + # Maximum size of a PDU (amount of OID mappings in a single SNMP request). The request will be split into multiple PDUs if mappings amount exceeds this number max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}" + # Delay after sending each request chunk (in case the request was split into multiple PDUs due to max_request_oids) + request_chunk_delay_ms: "${SNMP_REQUEST_CHUNK_DELAY_MS:100}" response: # To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types) ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" + scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" stats: # Enable/Disable the collection of transport statistics enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index a59013bbfe..6e30b06a2c 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -152,12 +152,14 @@ public class SnmpTransportContext extends TransportContext { try { if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); + sessionContext.setDevice(device); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); snmpTransportService.cancelQueryingTasks(sessionContext); snmpTransportService.createQueryingTasks(sessionContext); transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null); } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); + sessionContext.setDevice(device); sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); transportService.lifecycleEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), ComponentLifecycleEvent.UPDATED, true, null); } else { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 3a8811df97..26a1ef037e 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -15,6 +15,11 @@ */ package org.thingsboard.server.transport.snmp.service; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableScheduledFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import lombok.Builder; @@ -44,6 +49,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.kv.DataType; @@ -53,11 +59,11 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import org.thingsboard.server.transport.snmp.session.ScheduledTask; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -71,8 +77,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -80,6 +84,7 @@ import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") public class SnmpTransportService implements TbTransportService, CommandResponder { private final TransportService transportService; private final PduService pduService; @@ -88,23 +93,27 @@ public class SnmpTransportService implements TbTransportService, CommandResponde @Getter private Snmp snmp; - private ScheduledExecutorService queryingExecutor; - private ExecutorService responseProcessingExecutor; + private ListeningScheduledExecutorService scheduler; + private ExecutorService executor; private final Map responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.bind_port:1620}") private Integer snmpBindPort; - @Value("${transport.snmp.response_processing.parallelism_level}") - private Integer responseProcessingParallelismLevel; + @Value("${transport.snmp.response_processing.parallelism_level:4}") + private int responseProcessingThreadPoolSize; + @Value("${transport.snmp.scheduler_thread_pool_size:4}") + private int schedulerThreadPoolSize; @Value("${transport.snmp.underlying_protocol}") private String snmpUnderlyingProtocol; + @Value("${transport.snmp.request_chunk_delay_ms:100}") + private int requestChunkDelayMs; @PostConstruct private void init() throws IOException { - queryingExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("snmp-querying")); - responseProcessingExecutor = ThingsBoardExecutors.newWorkStealingPool(responseProcessingParallelismLevel, "snmp-response-processing"); + scheduler = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(schedulerThreadPoolSize, ThingsBoardThreadFactory.forName("snmp-querying"))); + executor = ThingsBoardExecutors.newWorkStealingPool(responseProcessingThreadPoolSize, "snmp-response-processing"); initializeSnmp(); configureResponseDataMappers(); @@ -115,11 +124,11 @@ public class SnmpTransportService implements TbTransportService, CommandResponde @PreDestroy public void stop() { - if (queryingExecutor != null) { - queryingExecutor.shutdownNow(); + if (scheduler != null) { + scheduler.shutdownNow(); } - if (responseProcessingExecutor != null) { - responseProcessingExecutor.shutdownNow(); + if (executor != null) { + executor.shutdownNow(); } } @@ -144,38 +153,39 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } public void createQueryingTasks(DeviceSessionContext sessionContext) { - List> queryingTasks = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() .filter(communicationConfig -> communicationConfig instanceof RepeatingQueryingSnmpCommunicationConfig) - .map(config -> { + .forEach(config -> { RepeatingQueryingSnmpCommunicationConfig repeatingCommunicationConfig = (RepeatingQueryingSnmpCommunicationConfig) config; Long queryingFrequency = repeatingCommunicationConfig.getQueryingFrequencyMs(); - return queryingExecutor.scheduleWithFixedDelay(() -> { + ScheduledTask scheduledTask = new ScheduledTask(); + scheduledTask.init(() -> { try { if (sessionContext.isActive()) { - sendRequest(sessionContext, repeatingCommunicationConfig); + return sendRequest(sessionContext, repeatingCommunicationConfig); } } catch (Exception e) { log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), config.getSpec().getLabel(), e); } - }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); - }) - .collect(Collectors.toList()); - sessionContext.getQueryingTasks().addAll(queryingTasks); + return Futures.immediateVoidFuture(); + }, queryingFrequency, scheduler); + sessionContext.getQueryingTasks().add(scheduledTask); + }); } public void cancelQueryingTasks(DeviceSessionContext sessionContext) { - sessionContext.getQueryingTasks().forEach(task -> task.cancel(true)); + sessionContext.getQueryingTasks().forEach(ScheduledTask::cancel); sessionContext.getQueryingTasks().clear(); } - private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { - sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); + private ListenableFuture sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { + return sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } - private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + private ListenableFuture sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { List request = pduService.createPdus(sessionContext, communicationConfig, values); RequestContext requestContext = RequestContext.builder() .communicationSpec(communicationConfig.getSpec()) @@ -183,19 +193,40 @@ public class SnmpTransportService implements TbTransportService, CommandResponde .responseMappings(communicationConfig.getAllMappings()) .requestSize(request.size()) .build(); - sendRequest(sessionContext, request, requestContext); + return sendRequest(sessionContext, request, requestContext); } - private void sendRequest(DeviceSessionContext sessionContext, List request, RequestContext requestContext) { - for (PDU pdu : request) { - log.debug("Executing SNMP request for device {} with {} variable bindings", sessionContext.getDeviceId(), pdu.size()); - try { - snmp.send(pdu, sessionContext.getTarget(), requestContext, sessionContext); - } catch (IOException e) { - log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); - transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e); + private ListenableFuture sendRequest(DeviceSessionContext sessionContext, List request, RequestContext requestContext) { + if (request.size() <= 1 || requestChunkDelayMs == 0) { + for (PDU pdu : request) { + sendPdu(pdu, requestContext, sessionContext); + } + return Futures.immediateVoidFuture(); + } + + List> futures = new ArrayList<>(); + for (int i = 0, delay = 0; i < request.size(); i++, delay += requestChunkDelayMs) { + PDU pdu = request.get(i); + if (delay == 0) { + sendPdu(pdu, requestContext, sessionContext); + } else { + ListenableScheduledFuture future = scheduler.schedule(() -> { + sendPdu(pdu, requestContext, sessionContext); + }, delay, TimeUnit.MILLISECONDS); + futures.add(future); } } + return Futures.whenAllComplete(futures).call(() -> null, MoreExecutors.directExecutor()); + } + + private void sendPdu(PDU pdu, RequestContext requestContext, DeviceSessionContext sessionContext) { + log.debug("[{}] Sending SNMP request with {} variable bindings to {}", sessionContext.getDeviceId(), pdu.size(), sessionContext.getTarget().getAddress()); + try { + snmp.send(pdu, sessionContext.getTarget(), requestContext, sessionContext); + } catch (Exception e) { + log.error("[{}] Failed to send SNMP request", sessionContext.getDeviceId(), e); + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), e); + } } public void onAttributeUpdate(DeviceSessionContext sessionContext, TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification) { @@ -251,21 +282,19 @@ public class SnmpTransportService implements TbTransportService, CommandResponde ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); RequestContext requestContext = (RequestContext) event.getUserObject(); if (event.getError() != null) { - log.warn("SNMP response error: {}", event.getError().toString()); + log.warn("[{}] SNMP response error: {}", sessionContext.getDeviceId(), event.getError().toString()); transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException(event.getError())); return; } PDU responsePdu = event.getResponse(); - if (log.isTraceEnabled()) { - log.trace("Received PDU for device {}: {}", sessionContext.getDeviceId(), responsePdu); - } + log.trace("[{}] Received PDU: {}", sessionContext.getDeviceId(), responsePdu); List response; if (requestContext.getRequestSize() == 1) { if (responsePdu == null) { - log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); if (requestContext.getMethod() == SnmpMethod.GET) { + log.debug("[{}][{}] Empty response from device", sessionContext.getDeviceId(), event.getRequest().getRequestID()); transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), requestContext.getCommunicationSpec().getLabel(), new RuntimeException("No response from device")); } return; @@ -281,14 +310,14 @@ public class SnmpTransportService implements TbTransportService, CommandResponde response.add(responsePart); } } - log.debug("All response parts are collected for request to device {}", sessionContext.getDeviceId()); + log.debug("[{}] All {} response parts are collected for request", sessionContext.getDeviceId(), responseParts.size()); } else { - log.trace("Awaiting other response parts for request to device {}", sessionContext.getDeviceId()); + log.trace("[{}] Awaiting other response parts for request", sessionContext.getDeviceId()); return; } } - responseProcessingExecutor.execute(() -> { + executor.execute(() -> { try { processResponse(sessionContext, response, requestContext); } catch (Exception e) { @@ -341,7 +370,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde .method(SnmpMethod.TRAP) .build(); - responseProcessingExecutor.execute(() -> { + executor.execute(() -> { processResponse(sessionContext, List.of(pdu), requestContext); }); } @@ -352,7 +381,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde JsonObject responseData = responseDataMappers.get(requestContext.getCommunicationSpec()).map(response, requestContext); if (responseData.size() == 0) { - log.warn("No values in the SNMP response for device {}", sessionContext.getDeviceId()); + log.warn("[{}] No values in the response", sessionContext.getDeviceId()); throw new IllegalArgumentException("No values in the response"); } @@ -428,11 +457,11 @@ public class SnmpTransportService implements TbTransportService, CommandResponde @PreDestroy public void shutdown() { log.info("Stopping SNMP transport!"); - if (queryingExecutor != null) { - queryingExecutor.shutdownNow(); + if (scheduler != null) { + scheduler.shutdownNow(); } - if (responseProcessingExecutor != null) { - responseProcessingExecutor.shutdownNow(); + if (executor != null) { + executor.shutdownNow(); } if (snmp != null) { try { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 5819c76f74..8ec0c6841d 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -45,7 +45,6 @@ import org.thingsboard.server.transport.snmp.SnmpTransportContext; import java.util.LinkedList; import java.util.List; import java.util.UUID; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @Slf4j @@ -60,7 +59,8 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Setter private SnmpDeviceTransportConfiguration deviceTransportConfiguration; @Getter - private final Device device; + @Setter + private Device device; @Getter private final TenantId tenantId; @@ -73,7 +73,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private Runnable sessionTimeoutHandler; @Getter - private final List> queryingTasks = new LinkedList<>(); + private final List queryingTasks = new LinkedList<>(); @Builder public DeviceSessionContext(TenantId tenantId, Device device, DeviceProfile deviceProfile, String token, diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/ScheduledTask.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/ScheduledTask.java new file mode 100644 index 0000000000..43d83e5e2c --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/ScheduledTask.java @@ -0,0 +1,62 @@ +/** + * 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.transport.snmp.session; + +import com.google.common.util.concurrent.AsyncCallable; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Data +@Slf4j +public class ScheduledTask { + private ListenableFuture scheduledFuture; + private boolean stopped = false; + + public void init(AsyncCallable task, long delayMs, ScheduledExecutorService scheduler) { + schedule(task, delayMs, scheduler); + } + + private void schedule(AsyncCallable task, long delayMs, ScheduledExecutorService scheduler) { + scheduledFuture = Futures.scheduleAsync(() -> { + if (stopped) { + return Futures.immediateCancelledFuture(); + } + try { + return task.call(); + } catch (Throwable t) { + log.error("Unhandled error in scheduled task", t); + return Futures.immediateFailedFuture(t); + } + }, delayMs, TimeUnit.MILLISECONDS, scheduler); + if (!stopped) { + scheduledFuture.addListener(() -> schedule(task, delayMs, scheduler), MoreExecutors.directExecutor()); + } + } + + public void cancel() { + stopped = true; + if (scheduledFuture != null) { + scheduledFuture.cancel(true); + } + } + +} diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java index 261e67bac8..ced8f21288 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java @@ -58,11 +58,10 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { private final Target target; private final Address address; + private final Map mappings; private Snmp snmp; - private final String password; - - public SnmpDeviceSimulatorV2(int port, String password) throws IOException { + public SnmpDeviceSimulatorV2(int port, String password, Map mappings) throws IOException { super(new File("conf.agent"), new File("bootCounter.agent"), new CommandProcessor(new OctetString("12312"))); CommunityTarget target = new CommunityTarget(); target.setCommunity(new OctetString(password)); @@ -72,7 +71,7 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { target.setTimeout(1500); target.setVersion(SnmpConstants.version2c); this.target = target; - this.password = password; + this.mappings = mappings; } public void start() throws IOException { @@ -85,13 +84,6 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { snmp = new Snmp(transportMappings[0]); } - public void setUpMappings(Map oidToResponseMappings) { - unregisterManagedObject(getSnmpv2MIB()); - oidToResponseMappings.forEach((oid, response) -> { - registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response))); - }); - } - public void sendTrap(String host, int port, Map values) throws IOException { PDU pdu = new PDU(); pdu.addAll(values.entrySet().stream() @@ -107,6 +99,10 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { @Override protected void registerManagedObjects() { + unregisterManagedObject(getSnmpv2MIB()); + mappings.forEach((oid, response) -> { + registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response))); + }); } protected void registerManagedObject(ManagedObject mo) { @@ -152,6 +148,7 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { } protected void unregisterManagedObjects() { + unregisterManagedObject(getSnmpv2MIB()); } protected void addCommunities(SnmpCommunityMIB communityMIB) { diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java index cb99c85655..c87507f962 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java @@ -15,28 +15,34 @@ */ package org.thingsboard.server.transport.snmp; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.thingsboard.common.util.JacksonUtil; + +import java.io.File; import java.io.IOException; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Scanner; +import java.util.stream.Collectors; public class SnmpTestV2 { private static final Scanner scanner = new Scanner(System.in); public static void main(String[] args) throws IOException { - SnmpDeviceSimulatorV2 client = new SnmpDeviceSimulatorV2(1610, "public"); + Map mappings = new LinkedHashMap<>(); + for (int i = 1; i <= 50; i++) { + String oid = String.format("1.3.6.1.2.1.%s.1.52", i); + mappings.put(oid, "value_" + i); + } - client.start(); - Map mappings = new HashMap<>(); -// for (int i = 1; i <= 500; i++) { -// String oid = String.format(".1.3.6.1.2.1.%s.1.52", i); -// mappings.put(oid, "value_" + i); -// } - mappings.put("1.3.6.1.2.1.266.1.52", "****"); + SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public", mappings); + device.start(); - client.setUpMappings(mappings); - inputTraps(client); + System.out.println("Hosting the following values:\n" + mappings.entrySet().stream() + .map(entry -> entry.getKey() + " - " + entry.getValue()) + .collect(Collectors.joining("\n"))); scanner.nextLine(); } @@ -53,4 +59,18 @@ public class SnmpTestV2 { } } + private static void updateDeviceProfile(String file) throws Exception { + File profileFile = new File(file); + JsonNode deviceProfile = JacksonUtil.OBJECT_MAPPER.readTree(profileFile); + ArrayNode mappingsJson = (ArrayNode) deviceProfile.at("/profileData/transportConfiguration/communicationConfigs/0/mappings"); + for (int i = 1; i <= 50; i++) { + String oid = String.format(".1.3.6.1.2.1.%s.1.52", i); + mappingsJson.add(JacksonUtil.newObjectNode() + .put("oid", oid) + .put("key", "key_" + i) + .put("dataType", "STRING")); + } + JacksonUtil.OBJECT_MAPPER.writeValue(profileFile, deviceProfile); + } + } diff --git a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json deleted file mode 100644 index f74ebca0bf..0000000000 --- a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "timeoutMs": 500, - "retries": 0, - "communicationConfigs": [ - { - "spec": "TELEMETRY_QUERYING", - "queryingFrequencyMs": 3000, - "mappings": [ - { - "oid": ".1.3.6.1.2.1.1.1.50", - "key": "temperature", - "dataType": "LONG" - }, - { - "oid": ".1.3.6.1.2.1.2.1.52", - "key": "humidity", - "dataType": "DOUBLE" - } - ] - }, - { - "spec": "CLIENT_ATTRIBUTES_QUERYING", - "queryingFrequencyMs": 5000, - "mappings": [ - { - "oid": ".1.3.6.1.2.1.3.1.54", - "key": "isCool", - "dataType": "STRING" - } - ] - }, - { - "spec": "SHARED_ATTRIBUTES_SETTING", - "mappings": [ - { - "oid": ".1.3.6.1.2.1.7.1.58", - "key": "shared", - "dataType": "STRING" - } - ] - } - ] -} diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json deleted file mode 100644 index 039e03fa53..0000000000 --- a/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "address": "192.168.3.23", - "port": 1610, - "protocolVersion": "V3", - - "username": "tb-user", - "engineId": "qwertyuioa", - "securityName": "tb-user", - "authenticationProtocol": "SHA_512", - "authenticationPassphrase": "sdfghjkloifgh", - "privacyProtocol": "DES", - "privacyPassphrase": "rtytguijokod" -} \ No newline at end of file diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json deleted file mode 100644 index c73d817bfb..0000000000 --- a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "address": "127.0.0.1", - "port": 1610, - "community": "public", - "protocolVersion": "V2C" -} \ No newline at end of file diff --git a/common/transport/snmp/src/test/resources/snmp_device_profile.json b/common/transport/snmp/src/test/resources/snmp_device_profile.json new file mode 100644 index 0000000000..0e72c46ce8 --- /dev/null +++ b/common/transport/snmp/src/test/resources/snmp_device_profile.json @@ -0,0 +1,289 @@ +{ + "name": "SNMP Device Profile", + "description": "", + "image": null, + "type": "DEFAULT", + "transportType": "SNMP", + "provisionType": "DISABLED", + "defaultRuleChainId": null, + "defaultDashboardId": null, + "defaultQueueName": null, + "profileData": { + "configuration": { + "type": "DEFAULT" + }, + "transportConfiguration": { + "type": "SNMP", + "timeoutMs": 500, + "retries": 0, + "communicationConfigs": [ + { + "spec": "TELEMETRY_QUERYING", + "mappings": [ + { + "oid": ".1.3.6.1.2.1.1.1.52", + "key": "key_1", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.2.1.52", + "key": "key_2", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.3.1.52", + "key": "key_3", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.4.1.52", + "key": "key_4", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.5.1.52", + "key": "key_5", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.6.1.52", + "key": "key_6", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.7.1.52", + "key": "key_7", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.8.1.52", + "key": "key_8", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.9.1.52", + "key": "key_9", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.10.1.52", + "key": "key_10", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.11.1.52", + "key": "key_11", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.12.1.52", + "key": "key_12", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.13.1.52", + "key": "key_13", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.14.1.52", + "key": "key_14", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.15.1.52", + "key": "key_15", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.16.1.52", + "key": "key_16", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.17.1.52", + "key": "key_17", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.18.1.52", + "key": "key_18", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.19.1.52", + "key": "key_19", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.20.1.52", + "key": "key_20", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.21.1.52", + "key": "key_21", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.22.1.52", + "key": "key_22", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.23.1.52", + "key": "key_23", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.24.1.52", + "key": "key_24", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.25.1.52", + "key": "key_25", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.26.1.52", + "key": "key_26", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.27.1.52", + "key": "key_27", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.28.1.52", + "key": "key_28", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.29.1.52", + "key": "key_29", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.30.1.52", + "key": "key_30", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.31.1.52", + "key": "key_31", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.32.1.52", + "key": "key_32", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.33.1.52", + "key": "key_33", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.34.1.52", + "key": "key_34", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.35.1.52", + "key": "key_35", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.36.1.52", + "key": "key_36", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.37.1.52", + "key": "key_37", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.38.1.52", + "key": "key_38", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.39.1.52", + "key": "key_39", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.40.1.52", + "key": "key_40", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.41.1.52", + "key": "key_41", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.42.1.52", + "key": "key_42", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.43.1.52", + "key": "key_43", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.44.1.52", + "key": "key_44", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.45.1.52", + "key": "key_45", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.46.1.52", + "key": "key_46", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.47.1.52", + "key": "key_47", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.48.1.52", + "key": "key_48", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.49.1.52", + "key": "key_49", + "dataType": "STRING" + }, + { + "oid": ".1.3.6.1.2.1.50.1.52", + "key": "key_50", + "dataType": "STRING" + } + ], + "queryingFrequencyMs": 5000 + } + ] + }, + "provisionConfiguration": { + "type": "DISABLED", + "provisionDeviceSecret": null + }, + "alarms": null + }, + "provisionDeviceKey": null, + "firmwareId": null, + "softwareId": null, + "defaultEdgeRuleChainId": null, + "default": false +} \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 0d7fbfc332..504b9bdf6d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -788,7 +788,11 @@ public class DefaultTransportService extends TransportActivityManager implements .setSuccess(success) .setError(error != null ? ExceptionUtils.getStackTrace(error) : "")) .build(); - sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + try { + sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + } catch (Exception e) { + log.error("[{}][{}] Failed to send lifecycle event to core", tenantId, deviceId, e); + } } @Override @@ -803,7 +807,11 @@ public class DefaultTransportService extends TransportActivityManager implements .setMethod(method) .setError(ExceptionUtils.getStackTrace(error))) .build(); - sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + try { + sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); + } catch (Exception e) { + log.error("[{}][{}] Failed to send error event to core", tenantId, deviceId, e); + } } @Override diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 805ab5a8dd..2e69032cb3 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -128,14 +128,17 @@ transport: bind_port: "${SNMP_BIND_PORT:1620}" response_processing: # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices - parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:4}" # to configure SNMP to work over UDP or TCP underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" - # Batch size to request OID mappings from the device (useful when the device profile has multiple hundreds of OID mappings) + # Maximum size of a PDU (amount of OID mappings in a single SNMP request). The request will be split into multiple PDUs if mappings amount exceeds this number max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}" + # Delay after sending each request chunk (in case the request was split into multiple PDUs due to max_request_oids) + request_chunk_delay_ms: "${SNMP_REQUEST_CHUNK_DELAY_MS:100}" response: # To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types) ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" + scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" sessions: # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device. # The parameter value is in milliseconds. From 1c59cef7eb23892dbb47ceae9bcd98d2cfdba8b6 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 6 Feb 2024 13:59:17 +0200 Subject: [PATCH 097/137] Add scheduler_thread_pool_size param description for SNMP --- application/src/main/resources/thingsboard.yml | 1 + transport/snmp/src/main/resources/tb-snmp-transport.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6a20c3bcfc..535d6189c8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -1183,6 +1183,7 @@ transport: response: # To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types) ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" + # Thread pool size for scheduler that executes device querying tasks scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" stats: # Enable/Disable the collection of transport statistics diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 2e69032cb3..a40374e1f6 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -138,6 +138,7 @@ transport: response: # To ignore SNMP response values that do not match the data type of the configured OID mapping (by default false - will throw an error if any value of the response not match configured data types) ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}" + # Thread pool size for scheduler that executes device querying tasks scheduler_thread_pool_size: "${SNMP_SCHEDULER_THREAD_POOL_SIZE:4}" sessions: # Session inactivity timeout is a global configuration parameter that defines how long the device transport session will be opened after the last message arrives from the device. From c6372e6e4e01c88bd18f60a02cf5c226b28f0325 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Tue, 6 Feb 2024 14:25:05 +0200 Subject: [PATCH 098/137] Added extra mapping for the gateway launch command docker compose file --- .../server/dao/util/DeviceConnectivityUtil.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index b0d42cdb07..f4a77dc725 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -112,12 +112,10 @@ public class DeviceConnectivityUtil { dockerComposeBuilder.append("# - \"5026:5026\" # Modbus TCP connector (Modbus Slave)\n"); dockerComposeBuilder.append("# - \"50000:50000/tcp\" # Socket connector with type TCP\n"); dockerComposeBuilder.append("# - \"50000:50000/udp\" # Socket connector with type UDP\n"); - if (isLocalhost(host)) { - dockerComposeBuilder.append("\n"); - dockerComposeBuilder.append(" # Necessary mapping for Linux\n"); - dockerComposeBuilder.append(" extra_hosts:\n"); - dockerComposeBuilder.append(" - \"host.docker.internal:host-gateway\"\n"); - } + dockerComposeBuilder.append("\n"); + dockerComposeBuilder.append(" # Necessary mapping for Linux\n"); + dockerComposeBuilder.append(" extra_hosts:\n"); + dockerComposeBuilder.append(" - \"host.docker.internal:host-gateway\"\n"); dockerComposeBuilder.append("\n"); dockerComposeBuilder.append(" # Environment variables\n"); dockerComposeBuilder.append(" environment:\n"); From e502c5ebbad9e6a331ca4ad40a1d950b93cd4200 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 6 Feb 2024 15:07:38 +0200 Subject: [PATCH 099/137] Revert "Init all actors for isolated rule engines" This reverts commit bf0cc22223b94fb0b73c55eba03a0237ca16eae4. --- .../server/actors/app/AppActor.java | 3 ++- .../server/actors/tenant/TenantActor.java | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index fcd6d7a1f9..98e0bf7c25 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -202,7 +202,8 @@ public class AppActor extends ContextAwareActor { return Optional.ofNullable(ctx.getOrCreateChildActor(new TbEntityActorId(tenantId), () -> DefaultActorService.TENANT_DISPATCHER_NAME, () -> new TenantActor.ActorCreator(systemContext, tenantId), - () -> true)); + () -> systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE) || + systemContext.getPartitionService().isManagedByCurrentService(tenantId))); } private void onToEdgeSessionMsg(EdgeSessionMsg msg) { diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 04c0f1c9e9..01b01a7d4a 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -90,16 +90,18 @@ public class TenantActor extends RuleChainManagerActor { isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); if (isRuleEngine) { - try { - if (getApiUsageState().isReExecEnabled()) { - log.debug("[{}] Going to init rule chains", tenantId); - initRuleChains(); - } else { - log.info("[{}] Skip init of the rule chains due to API limits", tenantId); + if (systemContext.getPartitionService().isManagedByCurrentService(tenantId)) { + try { + if (getApiUsageState().isReExecEnabled()) { + log.debug("[{}] Going to init rule chains", tenantId); + initRuleChains(); + } else { + log.info("[{}] Skip init of the rule chains due to API limits", tenantId); + } + } catch (Exception e) { + log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e); + cantFindTenant = true; } - } catch (Exception e) { - log.info("Failed to check ApiUsage \"ReExecEnabled\"!!!", e); - cantFindTenant = true; } } log.debug("[{}] Tenant actor started.", tenantId); From dec09fc479890cc3d051fa1f7f24854f0e4a7951 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 6 Feb 2024 15:21:57 +0200 Subject: [PATCH 100/137] extracted duplicated string to variable --- .../CassandraBaseTimeseriesDao.java | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 8d96dd293b..833a18ad39 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -86,6 +86,18 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD public static final String ASC_ORDER = "ASC"; public static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); protected static final List FIXED_PARTITION = List.of(0L); + private static String INSERT_WITH_NULL = INSERT_INTO + ModelConstants.TS_KV_CF + + "(" + ModelConstants.ENTITY_TYPE_COLUMN + + "," + ModelConstants.ENTITY_ID_COLUMN + + "," + ModelConstants.KEY_COLUMN + + "," + ModelConstants.PARTITION_COLUMN + + "," + ModelConstants.TS_COLUMN + + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + + "," + ModelConstants.STRING_VALUE_COLUMN + + "," + ModelConstants.LONG_VALUE_COLUMN + + "," + ModelConstants.DOUBLE_VALUE_COLUMN + + "," + ModelConstants.JSON_VALUE_COLUMN + ")" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; private CassandraTsPartitionsCache cassandraTsPartitionsCache; @@ -566,18 +578,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtCreationLock.lock(); try { if (saveWithNullStmt == null) { - saveWithNullStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + - "(" + ModelConstants.ENTITY_TYPE_COLUMN + - "," + ModelConstants.ENTITY_ID_COLUMN + - "," + ModelConstants.KEY_COLUMN + - "," + ModelConstants.PARTITION_COLUMN + - "," + ModelConstants.TS_COLUMN + - "," + ModelConstants.BOOLEAN_VALUE_COLUMN + - "," + ModelConstants.STRING_VALUE_COLUMN + - "," + ModelConstants.LONG_VALUE_COLUMN + - "," + ModelConstants.DOUBLE_VALUE_COLUMN + - "," + ModelConstants.JSON_VALUE_COLUMN + ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + saveWithNullStmt = prepare(INSERT_WITH_NULL); } } finally { stmtCreationLock.unlock(); @@ -591,18 +592,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtCreationLock.lock(); try { if (saveWithNullWithTtlStmt == null) { - saveWithNullWithTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + - "(" + ModelConstants.ENTITY_TYPE_COLUMN + - "," + ModelConstants.ENTITY_ID_COLUMN + - "," + ModelConstants.KEY_COLUMN + - "," + ModelConstants.PARTITION_COLUMN + - "," + ModelConstants.TS_COLUMN + - "," + ModelConstants.BOOLEAN_VALUE_COLUMN + - "," + ModelConstants.STRING_VALUE_COLUMN + - "," + ModelConstants.LONG_VALUE_COLUMN + - "," + ModelConstants.DOUBLE_VALUE_COLUMN + - "," + ModelConstants.JSON_VALUE_COLUMN + ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) USING TTL ?"); + saveWithNullWithTtlStmt = prepare(INSERT_WITH_NULL + " USING TTL ?"); } } finally { stmtCreationLock.unlock(); From 23545a64838746a4b322bf098defc3346d46a407 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 6 Feb 2024 15:28:55 +0200 Subject: [PATCH 101/137] CassandraBaseTimeseriesDao refactoring --- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 833a18ad39..6b2ab7e07b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -86,7 +86,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD public static final String ASC_ORDER = "ASC"; public static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); protected static final List FIXED_PARTITION = List.of(0L); - private static String INSERT_WITH_NULL = INSERT_INTO + ModelConstants.TS_KV_CF + + protected static final String INSERT_WITH_NULL = INSERT_INTO + ModelConstants.TS_KV_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + "," + ModelConstants.KEY_COLUMN + From cd46ebe53ae7f83163020d7ac986f4b0baa359fa Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 6 Feb 2024 16:57:52 +0200 Subject: [PATCH 102/137] Fix SNMP traps processing --- .../snmp/service/SnmpTransportService.java | 39 +++++++++++-------- .../service/DefaultTransportService.java | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 26a1ef037e..3dc293b70b 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -37,7 +37,7 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; -import org.snmp4j.smi.Address; +import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.TcpAddress; import org.snmp4j.smi.UdpAddress; @@ -327,24 +327,31 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } /* - * SNMP notifications handler - * - * TODO: add check for host uniqueness when saving device (for backward compatibility - only for the ones using from-device RPC requests) - * - * NOTE: SNMP TRAPs support won't work properly when there is more than one SNMP transport, - * due to load-balancing of requests from devices: session might not be on this instance - * */ + * SNMP notifications handler + * + * TODO: add check for host uniqueness when saving device (for backward compatibility - only for the ones using from-device RPC requests) + * + * NOTE: SNMP TRAPs support won't work properly when there is more than one SNMP transport, + * due to load-balancing of requests from devices: session might not be on this instance + * */ @Override public void processPdu(CommandResponderEvent event) { - Address sourceAddress = event.getPeerAddress(); - DeviceSessionContext sessionContext = transportContext.getSessions().stream() - .filter(session -> session.getTarget().getAddress().equals(sourceAddress)) - .findFirst().orElse(null); - if (sessionContext == null) { - log.warn("SNMP TRAP processing failed: couldn't find device session for address {}", sourceAddress); + IpAddress sourceAddress = (IpAddress) event.getPeerAddress(); + List sessions = transportContext.getSessions().stream() + .filter(session -> ((IpAddress) session.getTarget().getAddress()).getInetAddress().equals(sourceAddress.getInetAddress())) + .collect(Collectors.toList()); + if (sessions.isEmpty()) { + log.warn("Couldn't find device session for SNMP TRAP for address {}", sourceAddress); + return; + } else if (sessions.size() > 1) { + for (DeviceSessionContext sessionContext : sessions) { + transportService.errorEvent(sessionContext.getTenantId(), sessionContext.getDeviceId(), SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST.getLabel(), + new IllegalStateException("Found multiple devices for host " + sourceAddress.getInetAddress().getHostAddress())); + } return; } + DeviceSessionContext sessionContext = sessions.get(0); try { processIncomingTrap(sessionContext, event); } catch (Throwable e) { @@ -356,11 +363,11 @@ public class SnmpTransportService implements TbTransportService, CommandResponde private void processIncomingTrap(DeviceSessionContext sessionContext, CommandResponderEvent event) { PDU pdu = event.getPDU(); if (pdu == null) { - log.warn("Got empty trap from device {}", sessionContext.getDeviceId()); + log.warn("[{}] Received empty SNMP trap", sessionContext.getDeviceId()); throw new IllegalArgumentException("Received TRAP with no data"); } - log.debug("Processing SNMP trap from device {} (PDU: {}}", sessionContext.getDeviceId(), pdu); + log.debug("[{}] Processing SNMP trap: {}", sessionContext.getDeviceId(), pdu); SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() .filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_SERVER_RPC_REQUEST).findFirst() .orElseThrow(() -> new IllegalArgumentException("No config found for to-server RPC requests")); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 504b9bdf6d..14f7bfffff 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -805,7 +805,7 @@ public class DefaultTransportService extends TransportActivityManager implements .setEntityIdLSB(deviceId.getId().getLeastSignificantBits()) .setServiceId(serviceInfoProvider.getServiceId()) .setMethod(method) - .setError(ExceptionUtils.getStackTrace(error))) + .setError(ExceptionUtils.getRootCauseMessage(error))) .build(); try { sendToCore(tenantId, deviceId, msg, deviceId.getId(), TransportServiceCallback.EMPTY); From 78e746b10855b2c1f35ed825f3c43d817adfdb12 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 6 Feb 2024 17:47:40 +0200 Subject: [PATCH 103/137] UI: Update toast notification --- .../core/notification/notification.models.ts | 1 + .../home/models/widget-component.models.ts | 21 +++++----- .../components/snack-bar-component.html | 29 ++++++++++---- .../components/snack-bar-component.scss | 38 +++++++++++++++++++ .../app/shared/components/toast.directive.ts | 2 +- 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/core/notification/notification.models.ts b/ui-ngx/src/app/core/notification/notification.models.ts index 1d658a5b9e..37e2177d62 100644 --- a/ui-ngx/src/app/core/notification/notification.models.ts +++ b/ui-ngx/src/app/core/notification/notification.models.ts @@ -33,6 +33,7 @@ export class NotificationMessage { horizontalPosition?: NotificationHorizontalPosition; verticalPosition?: NotificationVerticalPosition; panelClass?: string | string[]; + modern?: boolean; } export class HideNotification { diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index a834120dc6..2abb5384b6 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -347,35 +347,35 @@ export class WidgetContext { showSuccessToast(message: string, duration: number = 1000, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', - target: string = 'dashboardRoot') { - this.showToast('success', message, duration, verticalPosition, horizontalPosition, target); + target: string = 'dashboardRoot', modern = false) { + this.showToast('success', message, duration, verticalPosition, horizontalPosition, target, modern); } showInfoToast(message: string, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', - target: string = 'dashboardRoot') { - this.showToast('info', message, undefined, verticalPosition, horizontalPosition, target); + target: string = 'dashboardRoot', modern = false) { + this.showToast('info', message, undefined, verticalPosition, horizontalPosition, target, modern); } showWarnToast(message: string, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', - target: string = 'dashboardRoot') { - this.showToast('warn', message, undefined, verticalPosition, horizontalPosition, target); + target: string = 'dashboardRoot', modern = false) { + this.showToast('warn', message, undefined, verticalPosition, horizontalPosition, target, modern); } showErrorToast(message: string, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', - target: string = 'dashboardRoot') { - this.showToast('error', message, undefined, verticalPosition, horizontalPosition, target); + target: string = 'dashboardRoot', modern = false) { + this.showToast('error', message, undefined, verticalPosition, horizontalPosition, target, modern); } showToast(type: NotificationType, message: string, duration: number, verticalPosition: NotificationVerticalPosition = 'bottom', horizontalPosition: NotificationHorizontalPosition = 'left', - target: string = 'dashboardRoot') { + target: string = 'dashboardRoot', modern = false) { this.store.dispatch(new ActionNotificationShow( { message, @@ -385,7 +385,8 @@ export class WidgetContext { horizontalPosition, target, panelClass: this.widgetNamespace, - forceDismiss: true + forceDismiss: true, + modern })); } diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.html b/ui-ngx/src/app/shared/components/snack-bar-component.html index cb39b98f48..ca70f6b476 100644 --- a/ui-ngx/src/app/shared/components/snack-bar-component.html +++ b/ui-ngx/src/app/shared/components/snack-bar-component.html @@ -15,15 +15,30 @@ limitations under the License. --> -
    -
    - + +
    + +
    + +
    +
    + +
    +
    diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.scss b/ui-ngx/src/app/shared/components/snack-bar-component.scss index 97531e220e..d86b740fc3 100644 --- a/ui-ngx/src/app/shared/components/snack-bar-component.scss +++ b/ui-ngx/src/app/shared/components/snack-bar-component.scss @@ -71,4 +71,42 @@ background: #008000; } } + + .tb-modern-toast { + display: flex; + flex-direction: column; + z-index: 1; + &-panel { + display: flex; + padding: 4px 4px 4px 12px; + justify-content: center; + align-items: center; + gap: 4px; + border-radius: 4px; + box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); + &.info-toast { + background: #e6e6e6; + color: rgba(50, 50, 50, 1); + } + &.warn-toast { + background: #fff3eb; + color: rgba(220, 109, 27, 1); + + } + &.error-toast { + background-color: #fff2f3; + color: rgba(209, 39, 48, 1); + } + &.success-toast { + background: #ebfcec; + color: rgba(0, 128, 0, 1); + } + } + .toast-text { + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + } + } } diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 6881460f6a..c096ba2a5a 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -280,7 +280,7 @@ export type ToastAnimationState = 'default' | 'opened' | 'closing'; }) export class TbSnackBarComponent implements AfterViewInit, OnDestroy { - @ViewChild('actionButton', {static: true}) actionButton: MatButton; + @ViewChild('actionButton') actionButton: MatButton; @HostBinding('class') get panelClass(): string[] { From f51df3211ddbbf642a15307503070e3f0d173a14 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Feb 2024 13:37:14 +0200 Subject: [PATCH 104/137] fixed saveDeviceWithAccessToken method to delete device if credentials saving failed --- .../server/dao/device/DeviceServiceImpl.java | 1 + .../server/dao/service/DeviceServiceTest.java | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index f190923167..f06dfac9f9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -157,6 +157,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService deviceDao.findDeviceByTenantIdAndName(tenantId.getId(), name).orElse(null), true); } + @Transactional @Override public Device saveDeviceWithAccessToken(Device device, String accessToken) { return doSaveDevice(device, accessToken, true); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java index 61d1c845fc..822f26496b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java @@ -35,8 +35,6 @@ import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; @@ -50,6 +48,7 @@ import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -129,6 +128,30 @@ public class DeviceServiceTest extends AbstractServiceTest { }); } + @Test + public void testSaveDevicesWithTheSameAccessToken() { + Device device = new Device(); + device.setTenantId(tenantId); + device.setName(StringUtils.randomAlphabetic(10)); + device.setType("default"); + String accessToken = StringUtils.generateSafeToken(10); + Device savedDevice = deviceService.saveDeviceWithAccessToken(device, accessToken); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); + Assert.assertEquals(accessToken, deviceCredentials.getCredentialsId()); + + Device duplicatedDevice = new Device(); + duplicatedDevice.setTenantId(tenantId); + duplicatedDevice.setName(StringUtils.randomAlphabetic(10)); + duplicatedDevice.setType("default"); + assertThatThrownBy(() -> deviceService.saveDeviceWithAccessToken(duplicatedDevice, accessToken)) + .isInstanceOf(DeviceCredentialsValidationException.class) + .hasMessageContaining("Device credentials are already assigned to another device!"); + + Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, duplicatedDevice.getName()); + Assertions.assertNull(deviceByName); + } + @Test public void testCountByTenantId() { Assert.assertEquals(0, deviceService.countByTenantId(tenantId)); From 07ff729d61b1ade57a3bc202f22b192de65052d1 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Feb 2024 15:23:31 +0200 Subject: [PATCH 105/137] added Transactional for saveDevice(Device device, boolean doValidate) and saveDevice(Device device) --- .../server/dao/device/DeviceServiceImpl.java | 2 + .../server/dao/service/DeviceServiceTest.java | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index f06dfac9f9..2f7fe0680c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -163,11 +163,13 @@ public class DeviceServiceImpl extends AbstractCachedEntityService idComparator = new IdComparator<>(); private TenantId anotherTenantId; @@ -152,6 +160,43 @@ public class DeviceServiceTest extends AbstractServiceTest { Assertions.assertNull(deviceByName); } + @Test + public void testShouldRollbackNotValidatedDeviceIfDeviceCredentialsValidationFailed() { + Mockito.reset(validator); + Mockito.doThrow(new DataValidationException("mock message")) + .when(validator).validate(any(), any()); + + Device device = new Device(); + device.setTenantId(tenantId); + device.setName(StringUtils.randomAlphabetic(10)); + device.setType("default"); + assertThatThrownBy(() -> deviceService.saveDevice(device, false)) + .isInstanceOf(DataValidationException.class) + .hasMessageContaining("mock message"); + + Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, device.getName()); + Assertions.assertNull(deviceByName); + } + + @Test + public void testShouldRollbackValidatedDeviceIfDeviceCredentialsValidationFailed() { + Mockito.reset(validator); + Mockito.doThrow(new DataValidationException("mock message")) + .when(validator).validate(any(), any()); + + Device device = new Device(); + device.setTenantId(tenantId); + device.setName(StringUtils.randomAlphabetic(10)); + device.setType("default"); + + assertThatThrownBy(() -> deviceService.saveDevice(device)) + .isInstanceOf(DataValidationException.class) + .hasMessageContaining("mock message"); + + Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, device.getName()); + Assertions.assertNull(deviceByName); + } + @Test public void testCountByTenantId() { Assert.assertEquals(0, deviceService.countByTenantId(tenantId)); From 986021fb2f753b649d715549623cd96c1cfe4151 Mon Sep 17 00:00:00 2001 From: Ivan Raznatovskyi Date: Thu, 8 Feb 2024 12:06:33 +0200 Subject: [PATCH 106/137] Fixed RPC connectors table collapsing also fixed JSON field height when this widget looks as a column --- .../widget/lib/gateway/gateway-connectors.component.html | 2 +- .../widget/lib/gateway/gateway-connectors.component.scss | 7 +++++++ .../widget/lib/gateway/gateway-connectors.component.ts | 1 + .../app/shared/components/json-object-edit.component.scss | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 8182920805..2b9329dc21 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -80,7 +80,7 @@ + [ngStyle.gt-md]="{ minWidth: '144px', maxWidth: '144px', width: '144px', textAlign: 'center'}"> {{ 'gateway.connectors-table-actions' | translate }} section:not(.table-section) { + max-width: unset; + @media #{$mat-gt-md} { + max-width: 50%; + } + } + .table-section { min-height: 35vh; overflow: hidden; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index a9465fc65b..eefd412133 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -381,6 +381,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie if ($event) { $event.stopPropagation(); } + this.initialConnector = attribute.value; const title = `Delete connector ${attribute.key}?`; const content = `All connector data will be deleted.`; this.dialogService.confirm(title, content, 'Cancel', 'Delete').subscribe(result => { diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.scss b/ui-ngx/src/app/shared/components/json-object-edit.component.scss index eb66f102a3..63884d518f 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.scss +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.scss @@ -52,6 +52,7 @@ width: 100%; min-width: 200px; height: 100%; + min-height: 300px; &:not(.fill-height) { min-height: 200px; From 0b1956be499fd03f89d10ed177e2c3add4db2a04 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 8 Feb 2024 14:30:10 +0200 Subject: [PATCH 107/137] UI: added image caching and async optimisation for map markers rendering --- ui-ngx/src/app/core/http/image.service.ts | 37 ++++++++++++++++--- .../components/widget/lib/maps/leaflet-map.ts | 23 +++++++++--- .../components/widget/lib/maps/markers.ts | 3 ++ 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/core/http/image.service.ts b/ui-ngx/src/app/core/http/image.service.ts index b0fb772130..45ba1bc4ad 100644 --- a/ui-ngx/src/app/core/http/image.service.ts +++ b/ui-ngx/src/app/core/http/image.service.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; -import { Observable, of } from 'rxjs'; +import { Observable, of, ReplaySubject } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { NO_IMAGE_DATA_URI, @@ -36,6 +36,9 @@ import { ResourcesService } from '@core/services/resources.service'; providedIn: 'root' }) export class ImageService { + + private imagesLoading: { [url: string]: ReplaySubject } = {}; + constructor( private http: HttpClient, private sanitizer: DomSanitizer, @@ -95,12 +98,34 @@ export class ImageService { parts[parts.length - 1] = encodeURIComponent(key); const encodedUrl = parts.join('/'); const imageLink = preview ? (encodedUrl + '/preview') : encodedUrl; - const options = defaultHttpOptionsFromConfig({ignoreLoading: true, ignoreErrors: true}); - return this.http - .get(imageLink, {...options, ...{ responseType: 'blob' } }).pipe( + return this.loadImageDataUrl(imageLink, asString, emptyUrl); + } + + private loadImageDataUrl(imageLink: string, asString = false, emptyUrl = NO_IMAGE_DATA_URI): Observable { + let request: ReplaySubject; + if (this.imagesLoading[imageLink]) { + request = this.imagesLoading[imageLink]; + } else { + request = new ReplaySubject(1); + this.imagesLoading[imageLink] = request; + const options = defaultHttpOptionsFromConfig({ignoreLoading: true, ignoreErrors: true}); + this.http.get(imageLink, {...options, ...{ responseType: 'blob' } }).subscribe({ + next: (value) => { + request.next(value); + request.complete(); + }, + error: err => { + request.error(err); + }, + complete: () => { + delete this.imagesLoading[imageLink]; + } + }); + } + return request.pipe( switchMap(val => blobToBase64(val).pipe( - map((dataUrl) => asString ? dataUrl : this.sanitizer.bypassSecurityTrustUrl(dataUrl)) - )), + map((dataUrl) => asString ? dataUrl : this.sanitizer.bypassSecurityTrustUrl(dataUrl)) + )), catchError(() => of(asString ? emptyUrl : this.sanitizer.bypassSecurityTrustUrl(emptyUrl))) ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 7f611bc16a..d58451d802 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -32,7 +32,7 @@ import { WidgetUnitedMapSettings } from './map-models'; import { Marker } from './markers'; -import { map, Observable, of, switchMap } from 'rxjs'; +import { map, Observable, of } from 'rxjs'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; import { Circle } from './circle'; @@ -64,6 +64,7 @@ import { MatDialog } from '@angular/material/dialog'; import { FormattedData, ReplaceInfo } from '@shared/models/widget.models'; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import { ImagePipe } from '@shared/pipe/image.pipe'; +import { take, tap } from 'rxjs/operators'; export default abstract class LeafletMap { @@ -940,7 +941,12 @@ export default abstract class LeafletMap { this.markersData = markersData; if (this.options.useClusterMarkers) { if (createdMarkers.length) { - this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); + createdMarkers.forEach((marker) => { + marker.createMarkerIconSubject.pipe( + tap(() => this.markersCluster.addLayer(marker.leafletMarker)), + take(1) + ).subscribe(); + }); } if (updatedMarkers.length) { this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)); @@ -971,10 +977,15 @@ export default abstract class LeafletMap { } this.markers.set(key, newMarker); if (!this.options.useClusterMarkers) { - this.map.addLayer(newMarker.leafletMarker); - if (this.map.pm.globalDragModeEnabled() && newMarker.leafletMarker.pm) { - newMarker.leafletMarker.pm.enableLayerDrag(); - } + newMarker.createMarkerIconSubject.pipe( + tap(() => { + this.map.addLayer(newMarker.leafletMarker); + if (this.map.pm.globalDragModeEnabled() && newMarker.leafletMarker.pm) { + newMarker.leafletMarker.pm.enableLayerDrag(); + } + }), + take(1) + ).subscribe(); } return newMarker; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index f5f302cee3..3b0c6efc6c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -23,6 +23,7 @@ import { fillDataPattern, isDefined, isDefinedAndNotNull, processDataPattern, sa import LeafletMap from './leaflet-map'; import { FormattedData } from '@shared/models/widget.models'; import { ImagePipe } from '@shared/pipe/image.pipe'; +import { ReplaySubject } from 'rxjs'; export class Marker { @@ -33,6 +34,7 @@ export class Marker { tooltipOffset: L.LatLngTuple; markerOffset: L.LatLngTuple; tooltip: L.Popup; + createMarkerIconSubject = new ReplaySubject(); constructor(private map: LeafletMap, private location: L.LatLng, @@ -148,6 +150,7 @@ export class Marker { this.labelOffset = [0, -iconInfo.size[1] * this.markerOffset[1] + 10]; } this.updateMarkerLabel(settings); + this.createMarkerIconSubject.next(iconInfo); }); } From 021252197f861b3796fc314efc0d73d0428fe433 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 8 Feb 2024 14:36:27 +0200 Subject: [PATCH 108/137] Fix compile error for BaseEdgeEventService --- .../org/thingsboard/server/dao/edge/BaseEdgeEventService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index 43c3dc4914..4d1e126c49 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.limits.RateLimitService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; @@ -28,7 +29,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.util.limits.RateLimitService; @Service @Slf4j From 7a28286b0530d4199fa8ee7ba41fc1535d5ffa0b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 8 Feb 2024 14:59:27 +0200 Subject: [PATCH 109/137] Hints and description improvements --- .../data/json/system/widget_bundles/buttons.json | 2 +- .../json/system/widget_types/action_button.json | 6 ++++-- .../json/system/widget_types/command_button.json | 7 +++++-- .../json/system/widget_types/single_switch.json | 7 +++++-- .../src/assets/locale/locale.constant-en_US.json | 14 +++++++------- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json index a48fba892b..8e7584d7c5 100644 --- a/application/src/main/data/json/system/widget_bundles/buttons.json +++ b/application/src/main/data/json/system/widget_bundles/buttons.json @@ -3,7 +3,7 @@ "alias": "buttons", "title": "Buttons", "image": "tb-image:YnV0dG9ucy5zdmc=:IkJ1dHRvbnMiIHN5c3RlbSBidW5kbGUgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzUiIHk9IjEyLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9IjEyLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTYyLjE2NjMgNTEuMzMzM1Y0NC4zMzMzSDY2LjgzM1Y1MS4zMzMzSDcyLjY2NjNWNDJINzYuMTY2M0w2NC40OTk3IDMxLjVMNTIuODMzIDQySDU2LjMzM1Y1MS4zMzMzSDYyLjE2NjNaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik05MC4xOTUzIDQyLjkzMTZIODYuMjFMODYuMTg4NSA0MC45NjU4SDg5LjY2ODlDOTAuMjU2MiA0MC45NjU4IDkwLjc1MzkgNDAuODc5OSA5MS4xNjIxIDQwLjcwOEM5MS41Nzc1IDQwLjUyOSA5MS44OTI2IDQwLjI3NDcgOTIuMTA3NCAzOS45NDUzQzkyLjMyMjMgMzkuNjA4NyA5Mi40Mjk3IDM5LjIwNDEgOTIuNDI5NyAzOC43MzE0QzkyLjQyOTcgMzguMjA4NyA5Mi4zMjk0IDM3Ljc4MjYgOTIuMTI4OSAzNy40NTMxQzkxLjkyODQgMzcuMTIzNyA5MS42MjA0IDM2Ljg4MzggOTEuMjA1MSAzNi43MzM0QzkwLjc5NjkgMzYuNTgzIDkwLjI3NDEgMzYuNTA3OCA4OS42MzY3IDM2LjUwNzhIODcuMDI2NFY1MEg4NC4zMzAxVjM0LjM1OTRIODkuNjM2N0M5MC40OTYxIDM0LjM1OTQgOTEuMjYyNCAzNC40NDE3IDkxLjkzNTUgMzQuNjA2NEM5Mi42MTU5IDM0Ljc3MTIgOTMuMTkyNCAzNS4wMjkgOTMuNjY1IDM1LjM3OTlDOTQuMTQ0OSAzNS43MjM2IDk0LjUwNjUgMzYuMTYwNSA5NC43NSAzNi42OTA0Qzk1LjAwMDcgMzcuMjIwNCA5NS4xMjYgMzcuODUwNiA5NS4xMjYgMzguNTgxMUM5NS4xMjYgMzkuMjI1NiA5NC45NzIgMzkuODE2NCA5NC42NjQxIDQwLjM1MzVDOTQuMzU2MSA0MC44ODM1IDkzLjkwMTQgNDEuMzE2NyA5My4yOTk4IDQxLjY1MzNDOTIuNjk4MiA0MS45ODk5IDkxLjk0OTkgNDIuMTkwNCA5MS4wNTQ3IDQyLjI1NDlMOTAuMTk1MyA0Mi45MzE2Wk05MC4wNzcxIDUwSDg1LjM2MTNMODYuNTc1MiA0Ny44NjIzSDkwLjA3NzFDOTAuNjg1OSA0Ny44NjIzIDkxLjE5NDMgNDcuNzYyIDkxLjYwMjUgNDcuNTYxNUM5Mi4wMTA3IDQ3LjM1MzggOTIuMzE1MSA0Ny4wNzEgOTIuNTE1NiA0Ni43MTI5QzkyLjcyMzMgNDYuMzQ3NyA5Mi44MjcxIDQ1LjkyMTUgOTIuODI3MSA0NS40MzQ2QzkyLjgyNzEgNDQuOTI2MSA5Mi43Mzc2IDQ0LjQ4NTcgOTIuNTU4NiA0NC4xMTMzQzkyLjM3OTYgNDMuNzMzNyA5Mi4wOTY3IDQzLjQ0MzcgOTEuNzEgNDMuMjQzMkM5MS4zMjMyIDQzLjAzNTUgOTAuODE4NCA0Mi45MzE2IDkwLjE5NTMgNDIuOTMxNkg4Ny4xNjZMODcuMTg3NSA0MC45NjU4SDkxLjEyOTlMOTEuNzQyMiA0MS43MDdDOTIuNjAxNiA0MS43MzU3IDkzLjMwNyA0MS45MjU1IDkzLjg1ODQgNDIuMjc2NEM5NC40MTcgNDIuNjI3MyA5NC44MzI0IDQzLjA4MiA5NS4xMDQ1IDQzLjY0MDZDOTUuMzc2NiA0NC4xOTkyIDk1LjUxMjcgNDQuODAwOCA5NS41MTI3IDQ1LjQ0NTNDOTUuNTEyNyA0Ni40NDA4IDk1LjI5NDMgNDcuMjc1MSA5NC44NTc0IDQ3Ljk0ODJDOTQuNDI3NyA0OC42MjE0IDkzLjgwODMgNDkuMTMzNSA5Mi45OTkgNDkuNDg0NEM5Mi4xODk4IDQ5LjgyODEgOTEuMjE1OCA1MCA5MC4wNzcxIDUwWk0xMDUuMjE2IDQ3LjI2MDdWMzguMzc3SDEwNy44MTVWNTBIMTA1LjM2NkwxMDUuMjE2IDQ3LjI2MDdaTTEwNS41ODEgNDQuODQzOEwxMDYuNDUxIDQ0LjgyMjNDMTA2LjQ1MSA0NS42MDI5IDEwNi4zNjUgNDYuMzIyNiAxMDYuMTkzIDQ2Ljk4MTRDMTA2LjAyMSA0Ny42MzMxIDEwNS43NTcgNDguMjAyNSAxMDUuMzk4IDQ4LjY4OTVDMTA1LjA0IDQ5LjE2OTMgMTA0LjU4MiA0OS41NDUyIDEwNC4wMjMgNDkuODE3NEMxMDMuNDY1IDUwLjA4MjQgMTAyLjc5NSA1MC4yMTQ4IDEwMi4wMTUgNTAuMjE0OEMxMDEuNDQ5IDUwLjIxNDggMTAwLjkzIDUwLjEzMjUgMTAwLjQ1NyA0OS45Njc4Qzk5Ljk4NDQgNDkuODAzMSA5OS41NzYyIDQ5LjU0ODggOTkuMjMyNCA0OS4yMDUxQzk4Ljg5NTggNDguODYxMyA5OC42MzQ0IDQ4LjQxMzcgOTguNDQ4MiA0Ny44NjIzQzk4LjI2MiA0Ny4zMTA5IDk4LjE2ODkgNDYuNjUyIDk4LjE2ODkgNDUuODg1N1YzOC4zNzdIMTAwLjc1OFY0NS45MDcyQzEwMC43NTggNDYuMzI5OCAxMDAuODA4IDQ2LjY4NDIgMTAwLjkwOCA0Ni45NzA3QzEwMS4wMDggNDcuMjUgMTAxLjE0NSA0Ny40NzU2IDEwMS4zMTYgNDcuNjQ3NUMxMDEuNDg4IDQ3LjgxOTMgMTAxLjY4OSA0Ny45NDExIDEwMS45MTggNDguMDEyN0MxMDIuMTQ3IDQ4LjA4NDMgMTAyLjM5MSA0OC4xMjAxIDEwMi42NDggNDguMTIwMUMxMDMuMzg2IDQ4LjEyMDEgMTAzLjk2NiA0Ny45NzY5IDEwNC4zODkgNDcuNjkwNEMxMDQuODE4IDQ3LjM5NjggMTA1LjEyMyA0Ny4wMDI5IDEwNS4zMDIgNDYuNTA4OEMxMDUuNDg4IDQ2LjAxNDYgMTA1LjU4MSA0NS40NTk2IDEwNS41ODEgNDQuODQzOFpNMTE2LjA0NyAzOC4zNzdWNDAuMjY3NkgxMDkuNDk0VjM4LjM3N0gxMTYuMDQ3Wk0xMTEuMzg1IDM1LjUzMDNIMTEzLjk3NFY0Ni43ODgxQzExMy45NzQgNDcuMTQ2MiAxMTQuMDI0IDQ3LjQyMTkgMTE0LjEyNCA0Ny42MTUyQzExNC4yMzEgNDcuODAxNCAxMTQuMzc4IDQ3LjkyNjggMTE0LjU2NCA0Ny45OTEyQzExNC43NTEgNDguMDU1NyAxMTQuOTY5IDQ4LjA4NzkgMTE1LjIyIDQ4LjA4NzlDMTE1LjM5OSA0OC4wODc5IDExNS41NzEgNDguMDc3MSAxMTUuNzM1IDQ4LjA1NTdDMTE1LjkgNDguMDM0MiAxMTYuMDMzIDQ4LjAxMjcgMTE2LjEzMyA0Ny45OTEyTDExNi4xNDQgNDkuOTY3OEMxMTUuOTI5IDUwLjAzMjIgMTE1LjY3OCA1MC4wODk1IDExNS4zOTIgNTAuMTM5NkMxMTUuMTEyIDUwLjE4OTggMTE0Ljc5IDUwLjIxNDggMTE0LjQyNSA1MC4yMTQ4QzExMy44MyA1MC4yMTQ4IDExMy4zMDQgNTAuMTExIDExMi44NDYgNDkuOTAzM0MxMTIuMzg3IDQ5LjY4ODUgMTEyLjAyOSA0OS4zNDExIDExMS43NzEgNDguODYxM0MxMTEuNTE0IDQ4LjM4MTUgMTExLjM4NSA0Ny43NDQxIDExMS4zODUgNDYuOTQ5MlYzNS41MzAzWk0xMjMuNjIzIDM4LjM3N1Y0MC4yNjc2SDExNy4wN1YzOC4zNzdIMTIzLjYyM1pNMTE4Ljk2MSAzNS41MzAzSDEyMS41NVY0Ni43ODgxQzEyMS41NSA0Ny4xNDYyIDEyMS42IDQ3LjQyMTkgMTIxLjcgNDcuNjE1MkMxMjEuODA4IDQ3LjgwMTQgMTIxLjk1NCA0Ny45MjY4IDEyMi4xNDEgNDcuOTkxMkMxMjIuMzI3IDQ4LjA1NTcgMTIyLjU0NSA0OC4wODc5IDEyMi43OTYgNDguMDg3OUMxMjIuOTc1IDQ4LjA4NzkgMTIzLjE0NyA0OC4wNzcxIDEyMy4zMTIgNDguMDU1N0MxMjMuNDc2IDQ4LjAzNDIgMTIzLjYwOSA0OC4wMTI3IDEyMy43MDkgNDcuOTkxMkwxMjMuNzIgNDkuOTY3OEMxMjMuNTA1IDUwLjAzMjIgMTIzLjI1NCA1MC4wODk1IDEyMi45NjggNTAuMTM5NkMxMjIuNjg4IDUwLjE4OTggMTIyLjM2NiA1MC4yMTQ4IDEyMi4wMDEgNTAuMjE0OEMxMjEuNDA3IDUwLjIxNDggMTIwLjg4IDUwLjExMSAxMjAuNDIyIDQ5LjkwMzNDMTE5Ljk2NCA0OS42ODg1IDExOS42MDUgNDkuMzQxMSAxMTkuMzQ4IDQ4Ljg2MTNDMTE5LjA5IDQ4LjM4MTUgMTE4Ljk2MSA0Ny43NDQxIDExOC45NjEgNDYuOTQ5MlYzNS41MzAzWk0xMjUuMTE5IDQ0LjMxNzRWNDQuMDcwM0MxMjUuMTE5IDQzLjIzMjQgMTI1LjI0MSA0Mi40NTU0IDEyNS40ODQgNDEuNzM5M0MxMjUuNzI4IDQxLjAxNiAxMjYuMDc5IDQwLjM4OTMgMTI2LjUzNyAzOS44NTk0QzEyNy4wMDMgMzkuMzIyMyAxMjcuNTY4IDM4LjkwNjkgMTI4LjIzNCAzOC42MTMzQzEyOC45MDggMzguMzEyNSAxMjkuNjY3IDM4LjE2MjEgMTMwLjUxMiAzOC4xNjIxQzEzMS4zNjQgMzguMTYyMSAxMzIuMTIzIDM4LjMxMjUgMTMyLjc4OSAzOC42MTMzQzEzMy40NjIgMzguOTA2OSAxMzQuMDMyIDM5LjMyMjMgMTM0LjQ5NyAzOS44NTk0QzEzNC45NjMgNDAuMzg5MyAxMzUuMzE3IDQxLjAxNiAxMzUuNTYxIDQxLjczOTNDMTM1LjgwNCA0Mi40NTU0IDEzNS45MjYgNDMuMjMyNCAxMzUuOTI2IDQ0LjA3MDNWNDQuMzE3NEMxMzUuOTI2IDQ1LjE1NTMgMTM1LjgwNCA0NS45MzIzIDEzNS41NjEgNDYuNjQ4NEMxMzUuMzE3IDQ3LjM2NDYgMTM0Ljk2MyA0Ny45OTEyIDEzNC40OTcgNDguNTI4M0MxMzQuMDMyIDQ5LjA1ODMgMTMzLjQ2NiA0OS40NzM2IDEzMi44IDQ5Ljc3NDRDMTMyLjEzNCA1MC4wNjggMTMxLjM3OCA1MC4yMTQ4IDEzMC41MzMgNTAuMjE0OEMxMjkuNjgxIDUwLjIxNDggMTI4LjkxOCA1MC4wNjggMTI4LjI0NSA0OS43NzQ0QzEyNy41NzkgNDkuNDczNiAxMjcuMDEzIDQ5LjA1ODMgMTI2LjU0OCA0OC41MjgzQzEyNi4wODIgNDcuOTkxMiAxMjUuNzI4IDQ3LjM2NDYgMTI1LjQ4NCA0Ni42NDg0QzEyNS4yNDEgNDUuOTMyMyAxMjUuMTE5IDQ1LjE1NTMgMTI1LjExOSA0NC4zMTc0Wk0xMjcuNzA4IDQ0LjA3MDNWNDQuMzE3NEMxMjcuNzA4IDQ0Ljg0MDIgMTI3Ljc2MiA0NS4zMzQzIDEyNy44NjkgNDUuNzk5OEMxMjcuOTc3IDQ2LjI2NTMgMTI4LjE0NSA0Ni42NzM1IDEyOC4zNzQgNDcuMDI0NEMxMjguNjAzIDQ3LjM3NTMgMTI4Ljg5NyA0Ny42NTEgMTI5LjI1NSA0Ny44NTE2QzEyOS42MTMgNDguMDUyMSAxMzAuMDM5IDQ4LjE1MjMgMTMwLjUzMyA0OC4xNTIzQzEzMS4wMTMgNDguMTUyMyAxMzEuNDI4IDQ4LjA1MjEgMTMxLjc3OSA0Ny44NTE2QzEzMi4xMzcgNDcuNjUxIDEzMi40MzEgNDcuMzc1MyAxMzIuNjYgNDcuMDI0NEMxMzIuODg5IDQ2LjY3MzUgMTMzLjA1OCA0Ni4yNjUzIDEzMy4xNjUgNDUuNzk5OEMxMzMuMjggNDUuMzM0MyAxMzMuMzM3IDQ0Ljg0MDIgMTMzLjMzNyA0NC4zMTc0VjQ0LjA3MDNDMTMzLjMzNyA0My41NTQ3IDEzMy4yOCA0My4wNjc3IDEzMy4xNjUgNDIuNjA5NEMxMzMuMDU4IDQyLjE0MzkgMTMyLjg4NiA0MS43MzIxIDEzMi42NDkgNDEuMzc0QzEzMi40MiA0MS4wMTYgMTMyLjEyNyA0MC43MzY3IDEzMS43NjkgNDAuNTM2MUMxMzEuNDE4IDQwLjMyODUgMTMwLjk5OSA0MC4yMjQ2IDEzMC41MTIgNDAuMjI0NkMxMzAuMDI1IDQwLjIyNDYgMTI5LjYwMiA0MC4zMjg1IDEyOS4yNDQgNDAuNTM2MUMxMjguODkzIDQwLjczNjcgMTI4LjYwMyA0MS4wMTYgMTI4LjM3NCA0MS4zNzRDMTI4LjE0NSA0MS43MzIxIDEyNy45NzcgNDIuMTQzOSAxMjcuODY5IDQyLjYwOTRDMTI3Ljc2MiA0My4wNjc3IDEyNy43MDggNDMuNTU0NyAxMjcuNzA4IDQ0LjA3MDNaTTE0MC45MTMgNDAuODU4NFY1MEgxMzguMzI0VjM4LjM3N0gxNDAuNzYzTDE0MC45MTMgNDAuODU4NFpNMTQwLjQ1MSA0My43NTg4TDEzOS42MTMgNDMuNzQ4QzEzOS42MiA0Mi45MjQ1IDEzOS43MzUgNDIuMTY4OSAxMzkuOTU3IDQxLjQ4MTRDMTQwLjE4NiA0MC43OTM5IDE0MC41MDEgNDAuMjAzMSAxNDAuOTAyIDM5LjcwOUMxNDEuMzExIDM5LjIxNDggMTQxLjc5OCAzOC44MzUzIDE0Mi4zNjMgMzguNTcwM0MxNDIuOTI5IDM4LjI5ODIgMTQzLjU1OSAzOC4xNjIxIDE0NC4yNTQgMzguMTYyMUMxNDQuODEyIDM4LjE2MjEgMTQ1LjMxNyAzOC4yNDA5IDE0NS43NjkgMzguMzk4NEMxNDYuMjI3IDM4LjU0ODggMTQ2LjYxNyAzOC43OTU5IDE0Ni45MzkgMzkuMTM5NkMxNDcuMjY5IDM5LjQ4MzQgMTQ3LjUyIDM5LjkzMSAxNDcuNjkxIDQwLjQ4MjRDMTQ3Ljg2MyA0MS4wMjY3IDE0Ny45NDkgNDEuNjk2MyAxNDcuOTQ5IDQyLjQ5MTJWNTBIMTQ1LjM1VjQyLjQ4MDVDMTQ1LjM1IDQxLjkyMTkgMTQ1LjI2NyA0MS40ODE0IDE0NS4xMDMgNDEuMTU5MkMxNDQuOTQ1IDQwLjgyOTggMTQ0LjcxMiA0MC41OTcgMTQ0LjQwNCA0MC40NjA5QzE0NC4xMDQgNDAuMzE3NyAxNDMuNzI4IDQwLjI0NjEgMTQzLjI3NiA0MC4yNDYxQzE0Mi44MzIgNDAuMjQ2MSAxNDIuNDM1IDQwLjMzOTIgMTQyLjA4NCA0MC41MjU0QzE0MS43MzMgNDAuNzExNiAxNDEuNDM2IDQwLjk2NTggMTQxLjE5MiA0MS4yODgxQzE0MC45NTYgNDEuNjEwNCAxNDAuNzczIDQxLjk4MjcgMTQwLjY0NSA0Mi40MDUzQzE0MC41MTYgNDIuODI3OCAxNDAuNDUxIDQzLjI3OSAxNDAuNDUxIDQzLjc1ODhaIiBmaWxsPSIjM0Y1MkREIi8+CjxyZWN0IHg9IjAuNzUiIHk9Ijg4Ljc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9Ijg4Ljc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTY1LjQ5OTcgMTExVjExMy4zMzNINzUuNTIxM0w2NC4zMzMgMTI0LjUyMkw2NS45NzggMTI2LjE2N0w3Ny4xNjYzIDExNC45NzhWMTI1SDc5LjQ5OTdWMTExSDY1LjQ5OTdaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik0xMDAuNTY0IDEyMS45NzJDMTAwLjU2NCAxMjEuNjQ5IDEwMC41MTQgMTIxLjM2MyAxMDAuNDE0IDEyMS4xMTJDMTAwLjMyMSAxMjAuODYyIDEwMC4xNTMgMTIwLjYzMiA5OS45MDkyIDEyMC40MjVDOTkuNjY1NyAxMjAuMjE3IDk5LjMyMTkgMTIwLjAxNyA5OC44Nzc5IDExOS44MjNDOTguNDQxMSAxMTkuNjIzIDk3Ljg4MjUgMTE5LjQxOSA5Ny4yMDIxIDExOS4yMTFDOTYuNDU3NCAxMTguOTgyIDk1Ljc2OTkgMTE4LjcyOCA5NS4xMzk2IDExOC40NDhDOTQuNTE2NiAxMTguMTYyIDkzLjk3MjMgMTE3LjgzMiA5My41MDY4IDExNy40NkM5My4wNDEzIDExNy4wOCA5Mi42Nzk3IDExNi42NDcgOTIuNDIxOSAxMTYuMTZDOTIuMTY0MSAxMTUuNjY2IDkyLjAzNTIgMTE1LjA5NyA5Mi4wMzUyIDExNC40NTJDOTIuMDM1MiAxMTMuODE1IDkyLjE2NzYgMTEzLjIzNSA5Mi40MzI2IDExMi43MTJDOTIuNzA0OCAxMTIuMTg5IDkzLjA4NzkgMTExLjczOCA5My41ODIgMTExLjM1OEM5NC4wODMzIDExMC45NzIgOTQuNjc0MiAxMTAuNjc0IDk1LjM1NDUgMTEwLjQ2N0M5Ni4wMzQ4IDExMC4yNTIgOTYuNzg2OCAxMTAuMTQ1IDk3LjYxMDQgMTEwLjE0NUM5OC43NzA1IDExMC4xNDUgOTkuNzY5NSAxMTAuMzU5IDEwMC42MDcgMTEwLjc4OUMxMDEuNDUyIDExMS4yMTkgMTAyLjEwMSAxMTEuNzk1IDEwMi41NTIgMTEyLjUxOUMxMDMuMDEgMTEzLjI0MiAxMDMuMjM5IDExNC4wNCAxMDMuMjM5IDExNC45MTRIMTAwLjU2NEMxMDAuNTY0IDExNC4zOTggMTAwLjQ1MyAxMTMuOTQ0IDEwMC4yMzEgMTEzLjU1QzEwMC4wMTcgMTEzLjE0OSA5OS42ODcyIDExMi44MzQgOTkuMjQzMiAxMTIuNjA0Qzk4LjgwNjMgMTEyLjM3NSA5OC4yNTEzIDExMi4yNjEgOTcuNTc4MSAxMTIuMjYxQzk2Ljk0MDggMTEyLjI2MSA5Ni40MTA4IDExMi4zNTcgOTUuOTg4MyAxMTIuNTUxQzk1LjU2NTggMTEyLjc0NCA5NS4yNTA3IDExMy4wMDYgOTUuMDQzIDExMy4zMzVDOTQuODM1MyAxMTMuNjY0IDk0LjczMTQgMTE0LjAzNyA5NC43MzE0IDExNC40NTJDOTQuNzMxNCAxMTQuNzQ2IDk0Ljc5OTUgMTE1LjAxNCA5NC45MzU1IDExNS4yNThDOTUuMDcxNiAxMTUuNDk0IDk1LjI3OTMgMTE1LjcxNiA5NS41NTg2IDExNS45MjRDOTUuODM3OSAxMTYuMTI0IDk2LjE4ODggMTE2LjMxNCA5Ni42MTEzIDExNi40OTNDOTcuMDMzOSAxMTYuNjcyIDk3LjUzMTYgMTE2Ljg0NCA5OC4xMDQ1IDExNy4wMDlDOTguOTcxIDExNy4yNjcgOTkuNzI2NiAxMTcuNTUzIDEwMC4zNzEgMTE3Ljg2OEMxMDEuMDE2IDExOC4xNzYgMTAxLjU1MyAxMTguNTI3IDEwMS45ODIgMTE4LjkyMUMxMDIuNDEyIDExOS4zMTUgMTAyLjczNCAxMTkuNzYyIDEwMi45NDkgMTIwLjI2NEMxMDMuMTY0IDEyMC43NTggMTAzLjI3MSAxMjEuMzIgMTAzLjI3MSAxMjEuOTVDMTAzLjI3MSAxMjIuNjA5IDEwMy4xMzkgMTIzLjIwMyAxMDIuODc0IDEyMy43MzNDMTAyLjYwOSAxMjQuMjU2IDEwMi4yMjkgMTI0LjcwNCAxMDEuNzM1IDEyNS4wNzZDMTAxLjI0OCAxMjUuNDQxIDEwMC42NjEgMTI1LjcyNCA5OS45NzM2IDEyNS45MjVDOTkuMjkzMyAxMjYuMTE4IDk4LjUzNDIgMTI2LjIxNSA5Ny42OTYzIDEyNi4yMTVDOTYuOTQ0MyAxMjYuMjE1IDk2LjIwMzEgMTI2LjExNSA5NS40NzI3IDEyNS45MTRDOTQuNzQ5MyAxMjUuNzE0IDk0LjA5MDUgMTI1LjQwOSA5My40OTYxIDEyNS4wMDFDOTIuOTAxNyAxMjQuNTg2IDkyLjQyOSAxMjQuMDcgOTIuMDc4MSAxMjMuNDU0QzkxLjcyNzIgMTIyLjgzMSA5MS41NTE4IDEyMi4xMDQgOTEuNTUxOCAxMjEuMjczSDk0LjI0OEM5NC4yNDggMTIxLjc4MiA5NC4zMzQgMTIyLjIxNSA5NC41MDU5IDEyMi41NzNDOTQuNjg0OSAxMjIuOTMxIDk0LjkzMiAxMjMuMjI1IDk1LjI0NzEgMTIzLjQ1NEM5NS41NjIyIDEyMy42NzYgOTUuOTI3NCAxMjMuODQxIDk2LjM0MjggMTIzLjk0OEM5Ni43NjUzIDEyNC4wNTYgOTcuMjE2NSAxMjQuMTA5IDk3LjY5NjMgMTI0LjEwOUM5OC4zMjY1IDEyNC4xMDkgOTguODUyOSAxMjQuMDIgOTkuMjc1NCAxMjMuODQxQzk5LjcwNTEgMTIzLjY2MiAxMDAuMDI3IDEyMy40MTEgMTAwLjI0MiAxMjMuMDg5QzEwMC40NTcgMTIyLjc2NyAxMDAuNTY0IDEyMi4zOTQgMTAwLjU2NCAxMjEuOTcyWk0xMTAuNzcyIDEyNi4yMTVDMTA5LjkxMyAxMjYuMjE1IDEwOS4xMzYgMTI2LjA3NSAxMDguNDQxIDEyNS43OTZDMTA3Ljc1NCAxMjUuNTA5IDEwNy4xNjcgMTI1LjExMiAxMDYuNjggMTI0LjYwNEMxMDYuMiAxMjQuMDk1IDEwNS44MzEgMTIzLjQ5NyAxMDUuNTczIDEyMi44MUMxMDUuMzE1IDEyMi4xMjIgMTA1LjE4NyAxMjEuMzgxIDEwNS4xODcgMTIwLjU4NlYxMjAuMTU2QzEwNS4xODcgMTE5LjI0NyAxMDUuMzE5IDExOC40MjMgMTA1LjU4NCAxMTcuNjg2QzEwNS44NDkgMTE2Ljk0OCAxMDYuMjE4IDExNi4zMTggMTA2LjY5IDExNS43OTVDMTA3LjE2MyAxMTUuMjY1IDEwNy43MjIgMTE0Ljg2IDEwOC4zNjYgMTE0LjU4MUMxMDkuMDExIDExNC4zMDIgMTA5LjcwOSAxMTQuMTYyIDExMC40NjEgMTE0LjE2MkMxMTEuMjkyIDExNC4xNjIgMTEyLjAxOSAxMTQuMzAyIDExMi42NDIgMTE0LjU4MUMxMTMuMjY1IDExNC44NiAxMTMuNzggMTE1LjI1NCAxMTQuMTg4IDExNS43NjNDMTE0LjYwNCAxMTYuMjY0IDExNC45MTIgMTE2Ljg2MiAxMTUuMTEyIDExNy41NTdDMTE1LjMyIDExOC4yNTEgMTE1LjQyNCAxMTkuMDE4IDExNS40MjQgMTE5Ljg1NVYxMjAuOTYySDEwNi40NDNWMTE5LjEwNEgxMTIuODY3VjExOC44OTlDMTEyLjg1MyAxMTguNDM0IDExMi43NiAxMTcuOTk3IDExMi41ODggMTE3LjU4OUMxMTIuNDIzIDExNy4xODEgMTEyLjE2OSAxMTYuODUxIDExMS44MjUgMTE2LjYwMUMxMTEuNDgxIDExNi4zNSAxMTEuMDIzIDExNi4yMjUgMTEwLjQ1IDExNi4yMjVDMTEwLjAyMSAxMTYuMjI1IDEwOS42MzcgMTE2LjMxOCAxMDkuMzAxIDExNi41MDRDMTA4Ljk3MSAxMTYuNjgzIDEwOC42OTYgMTE2Ljk0NCAxMDguNDc0IDExNy4yODhDMTA4LjI1MiAxMTcuNjMyIDEwOC4wOCAxMTguMDQ3IDEwNy45NTggMTE4LjUzNEMxMDcuODQzIDExOS4wMTQgMTA3Ljc4NiAxMTkuNTU1IDEwNy43ODYgMTIwLjE1NlYxMjAuNTg2QzEwNy43ODYgMTIxLjA5NCAxMDcuODU0IDEyMS41NjcgMTA3Ljk5IDEyMi4wMDRDMTA4LjEzMyAxMjIuNDM0IDEwOC4zNDEgMTIyLjgxIDEwOC42MTMgMTIzLjEzMkMxMDguODg1IDEyMy40NTQgMTA5LjIxNSAxMjMuNzA4IDEwOS42MDIgMTIzLjg5NUMxMDkuOTg4IDEyNC4wNzQgMTEwLjQyOSAxMjQuMTYzIDExMC45MjMgMTI0LjE2M0MxMTEuNTQ2IDEyNC4xNjMgMTEyLjEwMSAxMjQuMDM4IDExMi41ODggMTIzLjc4N0MxMTMuMDc1IDEyMy41MzYgMTEzLjQ5NyAxMjMuMTgyIDExMy44NTUgMTIyLjcyNEwxMTUuMjIgMTI0LjA0NUMxMTQuOTY5IDEyNC40MSAxMTQuNjQzIDEyNC43NjEgMTE0LjI0MiAxMjUuMDk4QzExMy44NDEgMTI1LjQyNyAxMTMuMzUxIDEyNS42OTYgMTEyLjc3MSAxMjUuOTAzQzExMi4xOTggMTI2LjExMSAxMTEuNTMyIDEyNi4yMTUgMTEwLjc3MiAxMjYuMjE1Wk0xMjAuMjYxIDExNi44NThWMTI2SDExNy42NzJWMTE0LjM3N0gxMjAuMTFMMTIwLjI2MSAxMTYuODU4Wk0xMTkuNzk5IDExOS43NTlMMTE4Ljk2MSAxMTkuNzQ4QzExOC45NjggMTE4LjkyNCAxMTkuMDgzIDExOC4xNjkgMTE5LjMwNSAxMTcuNDgxQzExOS41MzQgMTE2Ljc5NCAxMTkuODQ5IDExNi4yMDMgMTIwLjI1IDExNS43MDlDMTIwLjY1OCAxMTUuMjE1IDEyMS4xNDUgMTE0LjgzNSAxMjEuNzExIDExNC41N0MxMjIuMjc3IDExNC4yOTggMTIyLjkwNyAxMTQuMTYyIDEyMy42MDIgMTE0LjE2MkMxMjQuMTYgMTE0LjE2MiAxMjQuNjY1IDExNC4yNDEgMTI1LjExNiAxMTQuMzk4QzEyNS41NzUgMTE0LjU0OSAxMjUuOTY1IDExNC43OTYgMTI2LjI4NyAxMTUuMTRDMTI2LjYxNyAxMTUuNDgzIDEyNi44NjcgMTE1LjkzMSAxMjcuMDM5IDExNi40ODJDMTI3LjIxMSAxMTcuMDI3IDEyNy4yOTcgMTE3LjY5NiAxMjcuMjk3IDExOC40OTFWMTI2SDEyNC42OTdWMTE4LjQ4QzEyNC42OTcgMTE3LjkyMiAxMjQuNjE1IDExNy40ODEgMTI0LjQ1IDExNy4xNTlDMTI0LjI5MyAxMTYuODMgMTI0LjA2IDExNi41OTcgMTIzLjc1MiAxMTYuNDYxQzEyMy40NTEgMTE2LjMxOCAxMjMuMDc1IDExNi4yNDYgMTIyLjYyNCAxMTYuMjQ2QzEyMi4xOCAxMTYuMjQ2IDEyMS43ODMgMTE2LjMzOSAxMjEuNDMyIDExNi41MjVDMTIxLjA4MSAxMTYuNzEyIDEyMC43ODQgMTE2Ljk2NiAxMjAuNTQgMTE3LjI4OEMxMjAuMzA0IDExNy42MSAxMjAuMTIxIDExNy45ODMgMTE5Ljk5MiAxMTguNDA1QzExOS44NjMgMTE4LjgyOCAxMTkuNzk5IDExOS4yNzkgMTE5Ljc5OSAxMTkuNzU5Wk0xMzcuMjc5IDEyMy41OTRWMTA5LjVIMTM5Ljg3OVYxMjZIMTM3LjUyNkwxMzcuMjc5IDEyMy41OTRaTTEyOS43MTcgMTIwLjMxN1YxMjAuMDkyQzEyOS43MTcgMTE5LjIxMSAxMjkuODIxIDExOC40MDkgMTMwLjAyOCAxMTcuNjg2QzEzMC4yMzYgMTE2Ljk1NSAxMzAuNTM3IDExNi4zMjggMTMwLjkzMSAxMTUuODA2QzEzMS4zMjUgMTE1LjI3NiAxMzEuODA0IDExNC44NzEgMTMyLjM3IDExNC41OTJDMTMyLjkzNiAxMTQuMzA1IDEzMy41NzMgMTE0LjE2MiAxMzQuMjgyIDExNC4xNjJDMTM0Ljk4NCAxMTQuMTYyIDEzNS42IDExNC4yOTggMTM2LjEzIDExNC41N0MxMzYuNjYgMTE0Ljg0MiAxMzcuMTExIDExNS4yMzMgMTM3LjQ4MyAxMTUuNzQxQzEzNy44NTYgMTE2LjI0MyAxMzguMTUzIDExNi44NDQgMTM4LjM3NSAxMTcuNTQ2QzEzOC41OTcgMTE4LjI0MSAxMzguNzU1IDExOS4wMTQgMTM4Ljg0OCAxMTkuODY2VjEyMC41ODZDMTM4Ljc1NSAxMjEuNDE3IDEzOC41OTcgMTIyLjE3NiAxMzguMzc1IDEyMi44NjNDMTM4LjE1MyAxMjMuNTUxIDEzNy44NTYgMTI0LjE0NSAxMzcuNDgzIDEyNC42NDZDMTM3LjExMSAxMjUuMTQ4IDEzNi42NTYgMTI1LjUzNSAxMzYuMTE5IDEyNS44MDdDMTM1LjU4OSAxMjYuMDc5IDEzNC45NyAxMjYuMjE1IDEzNC4yNjEgMTI2LjIxNUMxMzMuNTU5IDEyNi4yMTUgMTMyLjkyNSAxMjYuMDY4IDEzMi4zNTkgMTI1Ljc3NEMxMzEuODAxIDEyNS40ODEgMTMxLjMyNSAxMjUuMDY5IDEzMC45MzEgMTI0LjUzOUMxMzAuNTM3IDEyNC4wMDkgMTMwLjIzNiAxMjMuMzg2IDEzMC4wMjggMTIyLjY3QzEyOS44MjEgMTIxLjk0NyAxMjkuNzE3IDEyMS4xNjIgMTI5LjcxNyAxMjAuMzE3Wk0xMzIuMzA2IDEyMC4wOTJWMTIwLjMxN0MxMzIuMzA2IDEyMC44NDcgMTMyLjM1MiAxMjEuMzQxIDEzMi40NDUgMTIxLjhDMTMyLjU0NiAxMjIuMjU4IDEzMi43IDEyMi42NjMgMTMyLjkwNyAxMjMuMDE0QzEzMy4xMTUgMTIzLjM1NyAxMzMuMzgzIDEyMy42MyAxMzMuNzEzIDEyMy44M0MxMzQuMDQ5IDEyNC4wMjMgMTM0LjQ1MSAxMjQuMTIgMTM0LjkxNiAxMjQuMTJDMTM1LjUwMyAxMjQuMTIgMTM1Ljk4NyAxMjMuOTkxIDEzNi4zNjYgMTIzLjczM0MxMzYuNzQ2IDEyMy40NzYgMTM3LjA0MyAxMjMuMTI4IDEzNy4yNTggMTIyLjY5MUMxMzcuNDggMTIyLjI0NyAxMzcuNjMgMTIxLjc1MyAxMzcuNzA5IDEyMS4yMDlWMTE5LjI2NUMxMzcuNjY2IDExOC44NDIgMTM3LjU3NiAxMTguNDQ4IDEzNy40NCAxMTguMDgzQzEzNy4zMTIgMTE3LjcxOCAxMzcuMTM2IDExNy4zOTkgMTM2LjkxNCAxMTcuMTI3QzEzNi42OTIgMTE2Ljg0OCAxMzYuNDE2IDExNi42MzMgMTM2LjA4NyAxMTYuNDgyQzEzNS43NjUgMTE2LjMyNSAxMzUuMzgyIDExNi4yNDYgMTM0LjkzOCAxMTYuMjQ2QzEzNC40NjUgMTE2LjI0NiAxMzQuMDY0IDExNi4zNDYgMTMzLjczNCAxMTYuNTQ3QzEzMy40MDUgMTE2Ljc0NyAxMzMuMTMzIDExNy4wMjMgMTMyLjkxOCAxMTcuMzc0QzEzMi43MSAxMTcuNzI1IDEzMi41NTYgMTE4LjEzMyAxMzIuNDU2IDExOC41OTlDMTMyLjM1NiAxMTkuMDY0IDEzMi4zMDYgMTE5LjU2MiAxMzIuMzA2IDEyMC4wOTJaIiBmaWxsPSIjM0Y1MkREIi8+Cjwvc3ZnPgo=", - "description": null, + "description": "Facilitates user interaction by enabling navigation between dashboard states, sending RPC commands to devices, and updating device attributes or time-series data.", "order": 7500, "name": "Buttons" }, diff --git a/application/src/main/data/json/system/widget_types/action_button.json b/application/src/main/data/json/system/widget_types/action_button.json index e6ffa8084e..a73aa198e7 100644 --- a/application/src/main/data/json/system/widget_types/action_button.json +++ b/application/src/main/data/json/system/widget_types/action_button.json @@ -3,7 +3,7 @@ "name": "Action button", "deprecated": false, "image": "tb-image:YWN0aW9uLWJ1dHRvbi5zdmc=:IkFjdGlvbiBidXR0b24iIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTYyLjE2NzMgODkuMzMzM1Y4Mi4zMzMzSDY2LjgzNFY4OS4zMzMzSDcyLjY2NzNWODBINzYuMTY3M0w2NC41MDA3IDY5LjVMNTIuODM0IDgwSDU2LjMzNFY4OS4zMzMzSDYyLjE2NzNaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik05MC4xOTUzIDgwLjkzMTZIODYuMjFMODYuMTg4NSA3OC45NjU4SDg5LjY2ODlDOTAuMjU2MiA3OC45NjU4IDkwLjc1MzkgNzguODc5OSA5MS4xNjIxIDc4LjcwOEM5MS41Nzc1IDc4LjUyOSA5MS44OTI2IDc4LjI3NDcgOTIuMTA3NCA3Ny45NDUzQzkyLjMyMjMgNzcuNjA4NyA5Mi40Mjk3IDc3LjIwNDEgOTIuNDI5NyA3Ni43MzE0QzkyLjQyOTcgNzYuMjA4NyA5Mi4zMjk0IDc1Ljc4MjYgOTIuMTI4OSA3NS40NTMxQzkxLjkyODQgNzUuMTIzNyA5MS42MjA0IDc0Ljg4MzggOTEuMjA1MSA3NC43MzM0QzkwLjc5NjkgNzQuNTgzIDkwLjI3NDEgNzQuNTA3OCA4OS42MzY3IDc0LjUwNzhIODcuMDI2NFY4OEg4NC4zMzAxVjcyLjM1OTRIODkuNjM2N0M5MC40OTYxIDcyLjM1OTQgOTEuMjYyNCA3Mi40NDE3IDkxLjkzNTUgNzIuNjA2NEM5Mi42MTU5IDcyLjc3MTIgOTMuMTkyNCA3My4wMjkgOTMuNjY1IDczLjM3OTlDOTQuMTQ0OSA3My43MjM2IDk0LjUwNjUgNzQuMTYwNSA5NC43NSA3NC42OTA0Qzk1LjAwMDcgNzUuMjIwNCA5NS4xMjYgNzUuODUwNiA5NS4xMjYgNzYuNTgxMUM5NS4xMjYgNzcuMjI1NiA5NC45NzIgNzcuODE2NCA5NC42NjQxIDc4LjM1MzVDOTQuMzU2MSA3OC44ODM1IDkzLjkwMTQgNzkuMzE2NyA5My4yOTk4IDc5LjY1MzNDOTIuNjk4MiA3OS45ODk5IDkxLjk0OTkgODAuMTkwNCA5MS4wNTQ3IDgwLjI1NDlMOTAuMTk1MyA4MC45MzE2Wk05MC4wNzcxIDg4SDg1LjM2MTNMODYuNTc1MiA4NS44NjIzSDkwLjA3NzFDOTAuNjg1OSA4NS44NjIzIDkxLjE5NDMgODUuNzYyIDkxLjYwMjUgODUuNTYxNUM5Mi4wMTA3IDg1LjM1MzggOTIuMzE1MSA4NS4wNzEgOTIuNTE1NiA4NC43MTI5QzkyLjcyMzMgODQuMzQ3NyA5Mi44MjcxIDgzLjkyMTUgOTIuODI3MSA4My40MzQ2QzkyLjgyNzEgODIuOTI2MSA5Mi43Mzc2IDgyLjQ4NTcgOTIuNTU4NiA4Mi4xMTMzQzkyLjM3OTYgODEuNzMzNyA5Mi4wOTY3IDgxLjQ0MzcgOTEuNzEgODEuMjQzMkM5MS4zMjMyIDgxLjAzNTUgOTAuODE4NCA4MC45MzE2IDkwLjE5NTMgODAuOTMxNkg4Ny4xNjZMODcuMTg3NSA3OC45NjU4SDkxLjEyOTlMOTEuNzQyMiA3OS43MDdDOTIuNjAxNiA3OS43MzU3IDkzLjMwNyA3OS45MjU1IDkzLjg1ODQgODAuMjc2NEM5NC40MTcgODAuNjI3MyA5NC44MzI0IDgxLjA4MiA5NS4xMDQ1IDgxLjY0MDZDOTUuMzc2NiA4Mi4xOTkyIDk1LjUxMjcgODIuODAwOCA5NS41MTI3IDgzLjQ0NTNDOTUuNTEyNyA4NC40NDA4IDk1LjI5NDMgODUuMjc1MSA5NC44NTc0IDg1Ljk0ODJDOTQuNDI3NyA4Ni42MjE0IDkzLjgwODMgODcuMTMzNSA5Mi45OTkgODcuNDg0NEM5Mi4xODk4IDg3LjgyODEgOTEuMjE1OCA4OCA5MC4wNzcxIDg4Wk0xMDUuMjE2IDg1LjI2MDdWNzYuMzc3SDEwNy44MTVWODhIMTA1LjM2NkwxMDUuMjE2IDg1LjI2MDdaTTEwNS41ODEgODIuODQzOEwxMDYuNDUxIDgyLjgyMjNDMTA2LjQ1MSA4My42MDI5IDEwNi4zNjUgODQuMzIyNiAxMDYuMTkzIDg0Ljk4MTRDMTA2LjAyMSA4NS42MzMxIDEwNS43NTcgODYuMjAyNSAxMDUuMzk4IDg2LjY4OTVDMTA1LjA0IDg3LjE2OTMgMTA0LjU4MiA4Ny41NDUyIDEwNC4wMjMgODcuODE3NEMxMDMuNDY1IDg4LjA4MjQgMTAyLjc5NSA4OC4yMTQ4IDEwMi4wMTUgODguMjE0OEMxMDEuNDQ5IDg4LjIxNDggMTAwLjkzIDg4LjEzMjUgMTAwLjQ1NyA4Ny45Njc4Qzk5Ljk4NDQgODcuODAzMSA5OS41NzYyIDg3LjU0ODggOTkuMjMyNCA4Ny4yMDUxQzk4Ljg5NTggODYuODYxMyA5OC42MzQ0IDg2LjQxMzcgOTguNDQ4MiA4NS44NjIzQzk4LjI2MiA4NS4zMTA5IDk4LjE2ODkgODQuNjUyIDk4LjE2ODkgODMuODg1N1Y3Ni4zNzdIMTAwLjc1OFY4My45MDcyQzEwMC43NTggODQuMzI5OCAxMDAuODA4IDg0LjY4NDIgMTAwLjkwOCA4NC45NzA3QzEwMS4wMDggODUuMjUgMTAxLjE0NSA4NS40NzU2IDEwMS4zMTYgODUuNjQ3NUMxMDEuNDg4IDg1LjgxOTMgMTAxLjY4OSA4NS45NDExIDEwMS45MTggODYuMDEyN0MxMDIuMTQ3IDg2LjA4NDMgMTAyLjM5MSA4Ni4xMjAxIDEwMi42NDggODYuMTIwMUMxMDMuMzg2IDg2LjEyMDEgMTAzLjk2NiA4NS45NzY5IDEwNC4zODkgODUuNjkwNEMxMDQuODE4IDg1LjM5NjggMTA1LjEyMyA4NS4wMDI5IDEwNS4zMDIgODQuNTA4OEMxMDUuNDg4IDg0LjAxNDYgMTA1LjU4MSA4My40NTk2IDEwNS41ODEgODIuODQzOFpNMTE2LjA0NyA3Ni4zNzdWNzguMjY3NkgxMDkuNDk0Vjc2LjM3N0gxMTYuMDQ3Wk0xMTEuMzg1IDczLjUzMDNIMTEzLjk3NFY4NC43ODgxQzExMy45NzQgODUuMTQ2MiAxMTQuMDI0IDg1LjQyMTkgMTE0LjEyNCA4NS42MTUyQzExNC4yMzEgODUuODAxNCAxMTQuMzc4IDg1LjkyNjggMTE0LjU2NCA4NS45OTEyQzExNC43NTEgODYuMDU1NyAxMTQuOTY5IDg2LjA4NzkgMTE1LjIyIDg2LjA4NzlDMTE1LjM5OSA4Ni4wODc5IDExNS41NzEgODYuMDc3MSAxMTUuNzM1IDg2LjA1NTdDMTE1LjkgODYuMDM0MiAxMTYuMDMzIDg2LjAxMjcgMTE2LjEzMyA4NS45OTEyTDExNi4xNDQgODcuOTY3OEMxMTUuOTI5IDg4LjAzMjIgMTE1LjY3OCA4OC4wODk1IDExNS4zOTIgODguMTM5NkMxMTUuMTEyIDg4LjE4OTggMTE0Ljc5IDg4LjIxNDggMTE0LjQyNSA4OC4yMTQ4QzExMy44MyA4OC4yMTQ4IDExMy4zMDQgODguMTExIDExMi44NDYgODcuOTAzM0MxMTIuMzg3IDg3LjY4ODUgMTEyLjAyOSA4Ny4zNDExIDExMS43NzEgODYuODYxM0MxMTEuNTE0IDg2LjM4MTUgMTExLjM4NSA4NS43NDQxIDExMS4zODUgODQuOTQ5MlY3My41MzAzWk0xMjMuNjIzIDc2LjM3N1Y3OC4yNjc2SDExNy4wN1Y3Ni4zNzdIMTIzLjYyM1pNMTE4Ljk2MSA3My41MzAzSDEyMS41NVY4NC43ODgxQzEyMS41NSA4NS4xNDYyIDEyMS42IDg1LjQyMTkgMTIxLjcgODUuNjE1MkMxMjEuODA4IDg1LjgwMTQgMTIxLjk1NCA4NS45MjY4IDEyMi4xNDEgODUuOTkxMkMxMjIuMzI3IDg2LjA1NTcgMTIyLjU0NSA4Ni4wODc5IDEyMi43OTYgODYuMDg3OUMxMjIuOTc1IDg2LjA4NzkgMTIzLjE0NyA4Ni4wNzcxIDEyMy4zMTIgODYuMDU1N0MxMjMuNDc2IDg2LjAzNDIgMTIzLjYwOSA4Ni4wMTI3IDEyMy43MDkgODUuOTkxMkwxMjMuNzIgODcuOTY3OEMxMjMuNTA1IDg4LjAzMjIgMTIzLjI1NCA4OC4wODk1IDEyMi45NjggODguMTM5NkMxMjIuNjg4IDg4LjE4OTggMTIyLjM2NiA4OC4yMTQ4IDEyMi4wMDEgODguMjE0OEMxMjEuNDA3IDg4LjIxNDggMTIwLjg4IDg4LjExMSAxMjAuNDIyIDg3LjkwMzNDMTE5Ljk2NCA4Ny42ODg1IDExOS42MDUgODcuMzQxMSAxMTkuMzQ4IDg2Ljg2MTNDMTE5LjA5IDg2LjM4MTUgMTE4Ljk2MSA4NS43NDQxIDExOC45NjEgODQuOTQ5MlY3My41MzAzWk0xMjUuMTE5IDgyLjMxNzRWODIuMDcwM0MxMjUuMTE5IDgxLjIzMjQgMTI1LjI0MSA4MC40NTU0IDEyNS40ODQgNzkuNzM5M0MxMjUuNzI4IDc5LjAxNiAxMjYuMDc5IDc4LjM4OTMgMTI2LjUzNyA3Ny44NTk0QzEyNy4wMDMgNzcuMzIyMyAxMjcuNTY4IDc2LjkwNjkgMTI4LjIzNCA3Ni42MTMzQzEyOC45MDggNzYuMzEyNSAxMjkuNjY3IDc2LjE2MjEgMTMwLjUxMiA3Ni4xNjIxQzEzMS4zNjQgNzYuMTYyMSAxMzIuMTIzIDc2LjMxMjUgMTMyLjc4OSA3Ni42MTMzQzEzMy40NjIgNzYuOTA2OSAxMzQuMDMyIDc3LjMyMjMgMTM0LjQ5NyA3Ny44NTk0QzEzNC45NjMgNzguMzg5MyAxMzUuMzE3IDc5LjAxNiAxMzUuNTYxIDc5LjczOTNDMTM1LjgwNCA4MC40NTU0IDEzNS45MjYgODEuMjMyNCAxMzUuOTI2IDgyLjA3MDNWODIuMzE3NEMxMzUuOTI2IDgzLjE1NTMgMTM1LjgwNCA4My45MzIzIDEzNS41NjEgODQuNjQ4NEMxMzUuMzE3IDg1LjM2NDYgMTM0Ljk2MyA4NS45OTEyIDEzNC40OTcgODYuNTI4M0MxMzQuMDMyIDg3LjA1ODMgMTMzLjQ2NiA4Ny40NzM2IDEzMi44IDg3Ljc3NDRDMTMyLjEzNCA4OC4wNjggMTMxLjM3OCA4OC4yMTQ4IDEzMC41MzMgODguMjE0OEMxMjkuNjgxIDg4LjIxNDggMTI4LjkxOCA4OC4wNjggMTI4LjI0NSA4Ny43NzQ0QzEyNy41NzkgODcuNDczNiAxMjcuMDEzIDg3LjA1ODMgMTI2LjU0OCA4Ni41MjgzQzEyNi4wODIgODUuOTkxMiAxMjUuNzI4IDg1LjM2NDYgMTI1LjQ4NCA4NC42NDg0QzEyNS4yNDEgODMuOTMyMyAxMjUuMTE5IDgzLjE1NTMgMTI1LjExOSA4Mi4zMTc0Wk0xMjcuNzA4IDgyLjA3MDNWODIuMzE3NEMxMjcuNzA4IDgyLjg0MDIgMTI3Ljc2MiA4My4zMzQzIDEyNy44NjkgODMuNzk5OEMxMjcuOTc3IDg0LjI2NTMgMTI4LjE0NSA4NC42NzM1IDEyOC4zNzQgODUuMDI0NEMxMjguNjAzIDg1LjM3NTMgMTI4Ljg5NyA4NS42NTEgMTI5LjI1NSA4NS44NTE2QzEyOS42MTMgODYuMDUyMSAxMzAuMDM5IDg2LjE1MjMgMTMwLjUzMyA4Ni4xNTIzQzEzMS4wMTMgODYuMTUyMyAxMzEuNDI4IDg2LjA1MjEgMTMxLjc3OSA4NS44NTE2QzEzMi4xMzcgODUuNjUxIDEzMi40MzEgODUuMzc1MyAxMzIuNjYgODUuMDI0NEMxMzIuODg5IDg0LjY3MzUgMTMzLjA1OCA4NC4yNjUzIDEzMy4xNjUgODMuNzk5OEMxMzMuMjggODMuMzM0MyAxMzMuMzM3IDgyLjg0MDIgMTMzLjMzNyA4Mi4zMTc0VjgyLjA3MDNDMTMzLjMzNyA4MS41NTQ3IDEzMy4yOCA4MS4wNjc3IDEzMy4xNjUgODAuNjA5NEMxMzMuMDU4IDgwLjE0MzkgMTMyLjg4NiA3OS43MzIxIDEzMi42NDkgNzkuMzc0QzEzMi40MiA3OS4wMTYgMTMyLjEyNyA3OC43MzY3IDEzMS43NjkgNzguNTM2MUMxMzEuNDE4IDc4LjMyODUgMTMwLjk5OSA3OC4yMjQ2IDEzMC41MTIgNzguMjI0NkMxMzAuMDI1IDc4LjIyNDYgMTI5LjYwMiA3OC4zMjg1IDEyOS4yNDQgNzguNTM2MUMxMjguODkzIDc4LjczNjcgMTI4LjYwMyA3OS4wMTYgMTI4LjM3NCA3OS4zNzRDMTI4LjE0NSA3OS43MzIxIDEyNy45NzcgODAuMTQzOSAxMjcuODY5IDgwLjYwOTRDMTI3Ljc2MiA4MS4wNjc3IDEyNy43MDggODEuNTU0NyAxMjcuNzA4IDgyLjA3MDNaTTE0MC45MTMgNzguODU4NFY4OEgxMzguMzI0Vjc2LjM3N0gxNDAuNzYzTDE0MC45MTMgNzguODU4NFpNMTQwLjQ1MSA4MS43NTg4TDEzOS42MTMgODEuNzQ4QzEzOS42MiA4MC45MjQ1IDEzOS43MzUgODAuMTY4OSAxMzkuOTU3IDc5LjQ4MTRDMTQwLjE4NiA3OC43OTM5IDE0MC41MDEgNzguMjAzMSAxNDAuOTAyIDc3LjcwOUMxNDEuMzExIDc3LjIxNDggMTQxLjc5OCA3Ni44MzUzIDE0Mi4zNjMgNzYuNTcwM0MxNDIuOTI5IDc2LjI5ODIgMTQzLjU1OSA3Ni4xNjIxIDE0NC4yNTQgNzYuMTYyMUMxNDQuODEyIDc2LjE2MjEgMTQ1LjMxNyA3Ni4yNDA5IDE0NS43NjkgNzYuMzk4NEMxNDYuMjI3IDc2LjU0ODggMTQ2LjYxNyA3Ni43OTU5IDE0Ni45MzkgNzcuMTM5NkMxNDcuMjY5IDc3LjQ4MzQgMTQ3LjUyIDc3LjkzMSAxNDcuNjkxIDc4LjQ4MjRDMTQ3Ljg2MyA3OS4wMjY3IDE0Ny45NDkgNzkuNjk2MyAxNDcuOTQ5IDgwLjQ5MTJWODhIMTQ1LjM1VjgwLjQ4MDVDMTQ1LjM1IDc5LjkyMTkgMTQ1LjI2NyA3OS40ODE0IDE0NS4xMDMgNzkuMTU5MkMxNDQuOTQ1IDc4LjgyOTggMTQ0LjcxMiA3OC41OTcgMTQ0LjQwNCA3OC40NjA5QzE0NC4xMDQgNzguMzE3NyAxNDMuNzI4IDc4LjI0NjEgMTQzLjI3NiA3OC4yNDYxQzE0Mi44MzIgNzguMjQ2MSAxNDIuNDM1IDc4LjMzOTIgMTQyLjA4NCA3OC41MjU0QzE0MS43MzMgNzguNzExNiAxNDEuNDM2IDc4Ljk2NTggMTQxLjE5MiA3OS4yODgxQzE0MC45NTYgNzkuNjEwNCAxNDAuNzczIDc5Ljk4MjcgMTQwLjY0NSA4MC40MDUzQzE0MC41MTYgODAuODI3OCAxNDAuNDUxIDgxLjI3OSAxNDAuNDUxIDgxLjc1ODhaIiBmaWxsPSIjM0Y1MkREIi8+Cjwvc3ZnPgo=", - "description": "Action button.", + "description": "Facilitates single-click navigation to other dashboards, states, or custom actions. Configurable settings allow for on-click action definition and conditions for button activation or deactivation. It offers various layouts and custom styling options for different states.", "descriptor": { "type": "latest", "sizeX": 3, @@ -22,6 +22,8 @@ "tags": [ "button", "action", - "navigation" + "navigation", + "navigate", + "dashboard state" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/command_button.json b/application/src/main/data/json/system/widget_types/command_button.json index 1ffd2326a8..dcfeb108f2 100644 --- a/application/src/main/data/json/system/widget_types/command_button.json +++ b/application/src/main/data/json/system/widget_types/command_button.json @@ -3,7 +3,7 @@ "name": "Command button", "deprecated": false, "image": "tb-image:Y29tbWFuZC1idXR0b24uc3Zn:IkNvbW1hbmQgYnV0dG9uIiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjAuNzUiIHk9IjUwLjc1IiB3aWR0aD0iMTk4LjUiIGhlaWdodD0iNTguNSIgcng9IjMuMjUiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPHBhdGggZD0iTTY1LjQ5OTcgNzNWNzUuMzMzM0g3NS41MjEzTDY0LjMzMyA4Ni41MjE3TDY1Ljk3OCA4OC4xNjY3TDc3LjE2NjMgNzYuOTc4M1Y4N0g3OS40OTk3VjczSDY1LjQ5OTdaIiBmaWxsPSIjM0Y1MkREIi8+CjxwYXRoIGQ9Ik0xMDAuNTY0IDgzLjk3MTdDMTAwLjU2NCA4My42NDk0IDEwMC41MTQgODMuMzYzIDEwMC40MTQgODMuMTEyM0MxMDAuMzIxIDgyLjg2MTcgMTAwLjE1MyA4Mi42MzI1IDk5LjkwOTIgODIuNDI0OEM5OS42NjU3IDgyLjIxNzEgOTkuMzIxOSA4Mi4wMTY2IDk4Ljg3NzkgODEuODIzMkM5OC40NDExIDgxLjYyMjcgOTcuODgyNSA4MS40MTg2IDk3LjIwMjEgODEuMjEwOUM5Ni40NTc0IDgwLjk4MTggOTUuNzY5OSA4MC43Mjc1IDk1LjEzOTYgODAuNDQ4MkM5NC41MTY2IDgwLjE2MTggOTMuOTcyMyA3OS44MzI0IDkzLjUwNjggNzkuNDZDOTMuMDQxMyA3OS4wODA0IDkyLjY3OTcgNzguNjQ3MSA5Mi40MjE5IDc4LjE2MDJDOTIuMTY0MSA3Ny42NjYgOTIuMDM1MiA3Ny4wOTY3IDkyLjAzNTIgNzYuNDUyMUM5Mi4wMzUyIDc1LjgxNDggOTIuMTY3NiA3NS4yMzQ3IDkyLjQzMjYgNzQuNzExOUM5Mi43MDQ4IDc0LjE4OTEgOTMuMDg3OSA3My43MzggOTMuNTgyIDczLjM1ODRDOTQuMDgzMyA3Mi45NzE3IDk0LjY3NDIgNzIuNjc0NSA5NS4zNTQ1IDcyLjQ2NjhDOTYuMDM0OCA3Mi4yNTIgOTYuNzg2OCA3Mi4xNDQ1IDk3LjYxMDQgNzIuMTQ0NUM5OC43NzA1IDcyLjE0NDUgOTkuNzY5NSA3Mi4zNTk0IDEwMC42MDcgNzIuNzg5MUMxMDEuNDUyIDczLjIxODggMTAyLjEwMSA3My43OTUyIDEwMi41NTIgNzQuNTE4NkMxMDMuMDEgNzUuMjQxOSAxMDMuMjM5IDc2LjA0MDQgMTAzLjIzOSA3Ni45MTQxSDEwMC41NjRDMTAwLjU2NCA3Ni4zOTg0IDEwMC40NTMgNzUuOTQzNyAxMDAuMjMxIDc1LjU0OThDMTAwLjAxNyA3NS4xNDg4IDk5LjY4NzIgNzQuODMzNyA5OS4yNDMyIDc0LjYwNDVDOTguODA2MyA3NC4zNzUzIDk4LjI1MTMgNzQuMjYwNyA5Ny41NzgxIDc0LjI2MDdDOTYuOTQwOCA3NC4yNjA3IDk2LjQxMDggNzQuMzU3NCA5NS45ODgzIDc0LjU1MDhDOTUuNTY1OCA3NC43NDQxIDk1LjI1MDcgNzUuMDA1NSA5NS4wNDMgNzUuMzM1Qzk0LjgzNTMgNzUuNjY0NCA5NC43MzE0IDc2LjAzNjggOTQuNzMxNCA3Ni40NTIxQzk0LjczMTQgNzYuNzQ1OCA5NC43OTk1IDc3LjAxNDMgOTQuOTM1NSA3Ny4yNTc4Qzk1LjA3MTYgNzcuNDk0MSA5NS4yNzkzIDc3LjcxNjEgOTUuNTU4NiA3Ny45MjM4Qzk1LjgzNzkgNzguMTI0MyA5Ni4xODg4IDc4LjMxNDEgOTYuNjExMyA3OC40OTMyQzk3LjAzMzkgNzguNjcyMiA5Ny41MzE2IDc4Ljg0NDEgOTguMTA0NSA3OS4wMDg4Qzk4Ljk3MSA3OS4yNjY2IDk5LjcyNjYgNzkuNTUzMSAxMDAuMzcxIDc5Ljg2ODJDMTAxLjAxNiA4MC4xNzYxIDEwMS41NTMgODAuNTI3IDEwMS45ODIgODAuOTIwOUMxMDIuNDEyIDgxLjMxNDggMTAyLjczNCA4MS43NjI0IDEwMi45NDkgODIuMjYzN0MxMDMuMTY0IDgyLjc1NzggMTAzLjI3MSA4My4zMiAxMDMuMjcxIDgzLjk1MDJDMTAzLjI3MSA4NC42MDkgMTAzLjEzOSA4NS4yMDM1IDEwMi44NzQgODUuNzMzNEMxMDIuNjA5IDg2LjI1NjIgMTAyLjIyOSA4Ni43MDM4IDEwMS43MzUgODcuMDc2MkMxMDEuMjQ4IDg3LjQ0MTQgMTAwLjY2MSA4Ny43MjQzIDk5Ljk3MzYgODcuOTI0OEM5OS4yOTMzIDg4LjExODIgOTguNTM0MiA4OC4yMTQ4IDk3LjY5NjMgODguMjE0OEM5Ni45NDQzIDg4LjIxNDggOTYuMjAzMSA4OC4xMTQ2IDk1LjQ3MjcgODcuOTE0MUM5NC43NDkzIDg3LjcxMzUgOTQuMDkwNSA4Ny40MDkyIDkzLjQ5NjEgODcuMDAxQzkyLjkwMTcgODYuNTg1NiA5Mi40MjkgODYuMDcgOTIuMDc4MSA4NS40NTQxQzkxLjcyNzIgODQuODMxMSA5MS41NTE4IDg0LjEwNDIgOTEuNTUxOCA4My4yNzM0SDk0LjI0OEM5NC4yNDggODMuNzgxOSA5NC4zMzQgODQuMjE1MiA5NC41MDU5IDg0LjU3MzJDOTQuNjg0OSA4NC45MzEzIDk0LjkzMiA4NS4yMjQ5IDk1LjI0NzEgODUuNDU0MUM5NS41NjIyIDg1LjY3NjEgOTUuOTI3NCA4NS44NDA4IDk2LjM0MjggODUuOTQ4MkM5Ni43NjUzIDg2LjA1NTcgOTcuMjE2NSA4Ni4xMDk0IDk3LjY5NjMgODYuMTA5NEM5OC4zMjY1IDg2LjEwOTQgOTguODUyOSA4Ni4wMTk5IDk5LjI3NTQgODUuODQwOEM5OS43MDUxIDg1LjY2MTggMTAwLjAyNyA4NS40MTExIDEwMC4yNDIgODUuMDg4OUMxMDAuNDU3IDg0Ljc2NjYgMTAwLjU2NCA4NC4zOTQyIDEwMC41NjQgODMuOTcxN1pNMTEwLjc3MiA4OC4yMTQ4QzEwOS45MTMgODguMjE0OCAxMDkuMTM2IDg4LjA3NTIgMTA4LjQ0MSA4Ny43OTU5QzEwNy43NTQgODcuNTA5NCAxMDcuMTY3IDg3LjExMiAxMDYuNjggODYuNjAzNUMxMDYuMiA4Ni4wOTUxIDEwNS44MzEgODUuNDk3MSAxMDUuNTczIDg0LjgwOTZDMTA1LjMxNSA4NC4xMjIxIDEwNS4xODcgODMuMzgwOSAxMDUuMTg3IDgyLjU4NTlWODIuMTU2MkMxMDUuMTg3IDgxLjI0NjcgMTA1LjMxOSA4MC40MjMyIDEwNS41ODQgNzkuNjg1NUMxMDUuODQ5IDc4Ljk0NzkgMTA2LjIxOCA3OC4zMTc3IDEwNi42OSA3Ny43OTQ5QzEwNy4xNjMgNzcuMjY1IDEwNy43MjIgNzYuODYwNCAxMDguMzY2IDc2LjU4MTFDMTA5LjAxMSA3Ni4zMDE4IDEwOS43MDkgNzYuMTYyMSAxMTAuNDYxIDc2LjE2MjFDMTExLjI5MiA3Ni4xNjIxIDExMi4wMTkgNzYuMzAxOCAxMTIuNjQyIDc2LjU4MTFDMTEzLjI2NSA3Ni44NjA0IDExMy43OCA3Ny4yNTQyIDExNC4xODggNzcuNzYyN0MxMTQuNjA0IDc4LjI2NCAxMTQuOTEyIDc4Ljg2MiAxMTUuMTEyIDc5LjU1NjZDMTE1LjMyIDgwLjI1MTMgMTE1LjQyNCA4MS4wMTc2IDExNS40MjQgODEuODU1NVY4Mi45NjE5SDEwNi40NDNWODEuMTAzNUgxMTIuODY3VjgwLjg5OTRDMTEyLjg1MyA4MC40MzM5IDExMi43NiA3OS45OTcxIDExMi41ODggNzkuNTg4OUMxMTIuNDIzIDc5LjE4MDcgMTEyLjE2OSA3OC44NTEyIDExMS44MjUgNzguNjAwNkMxMTEuNDgxIDc4LjM0OTkgMTExLjAyMyA3OC4yMjQ2IDExMC40NSA3OC4yMjQ2QzExMC4wMjEgNzguMjI0NiAxMDkuNjM3IDc4LjMxNzcgMTA5LjMwMSA3OC41MDM5QzEwOC45NzEgNzguNjgyOSAxMDguNjk2IDc4Ljk0NDMgMTA4LjQ3NCA3OS4yODgxQzEwOC4yNTIgNzkuNjMxOCAxMDguMDggODAuMDQ3MiAxMDcuOTU4IDgwLjUzNDJDMTA3Ljg0MyA4MS4wMTQgMTA3Ljc4NiA4MS41NTQ3IDEwNy43ODYgODIuMTU2MlY4Mi41ODU5QzEwNy43ODYgODMuMDk0NCAxMDcuODU0IDgzLjU2NzEgMTA3Ljk5IDg0LjAwMzlDMTA4LjEzMyA4NC40MzM2IDEwOC4zNDEgODQuODA5NiAxMDguNjEzIDg1LjEzMThDMTA4Ljg4NSA4NS40NTQxIDEwOS4yMTUgODUuNzA4MyAxMDkuNjAyIDg1Ljg5NDVDMTA5Ljk4OCA4Ni4wNzM2IDExMC40MjkgODYuMTYzMSAxMTAuOTIzIDg2LjE2MzFDMTExLjU0NiA4Ni4xNjMxIDExMi4xMDEgODYuMDM3OCAxMTIuNTg4IDg1Ljc4NzFDMTEzLjA3NSA4NS41MzY1IDExMy40OTcgODUuMTgyIDExMy44NTUgODQuNzIzNkwxMTUuMjIgODYuMDQ0OUMxMTQuOTY5IDg2LjQxMDIgMTE0LjY0MyA4Ni43NjExIDExNC4yNDIgODcuMDk3N0MxMTMuODQxIDg3LjQyNzEgMTEzLjM1MSA4Ny42OTU2IDExMi43NzEgODcuOTAzM0MxMTIuMTk4IDg4LjExMSAxMTEuNTMyIDg4LjIxNDggMTEwLjc3MiA4OC4yMTQ4Wk0xMjAuMjYxIDc4Ljg1ODRWODhIMTE3LjY3MlY3Ni4zNzdIMTIwLjExTDEyMC4yNjEgNzguODU4NFpNMTE5Ljc5OSA4MS43NTg4TDExOC45NjEgODEuNzQ4QzExOC45NjggODAuOTI0NSAxMTkuMDgzIDgwLjE2ODkgMTE5LjMwNSA3OS40ODE0QzExOS41MzQgNzguNzkzOSAxMTkuODQ5IDc4LjIwMzEgMTIwLjI1IDc3LjcwOUMxMjAuNjU4IDc3LjIxNDggMTIxLjE0NSA3Ni44MzUzIDEyMS43MTEgNzYuNTcwM0MxMjIuMjc3IDc2LjI5ODIgMTIyLjkwNyA3Ni4xNjIxIDEyMy42MDIgNzYuMTYyMUMxMjQuMTYgNzYuMTYyMSAxMjQuNjY1IDc2LjI0MDkgMTI1LjExNiA3Ni4zOTg0QzEyNS41NzUgNzYuNTQ4OCAxMjUuOTY1IDc2Ljc5NTkgMTI2LjI4NyA3Ny4xMzk2QzEyNi42MTcgNzcuNDgzNCAxMjYuODY3IDc3LjkzMSAxMjcuMDM5IDc4LjQ4MjRDMTI3LjIxMSA3OS4wMjY3IDEyNy4yOTcgNzkuNjk2MyAxMjcuMjk3IDgwLjQ5MTJWODhIMTI0LjY5N1Y4MC40ODA1QzEyNC42OTcgNzkuOTIxOSAxMjQuNjE1IDc5LjQ4MTQgMTI0LjQ1IDc5LjE1OTJDMTI0LjI5MyA3OC44Mjk4IDEyNC4wNiA3OC41OTcgMTIzLjc1MiA3OC40NjA5QzEyMy40NTEgNzguMzE3NyAxMjMuMDc1IDc4LjI0NjEgMTIyLjYyNCA3OC4yNDYxQzEyMi4xOCA3OC4yNDYxIDEyMS43ODMgNzguMzM5MiAxMjEuNDMyIDc4LjUyNTRDMTIxLjA4MSA3OC43MTE2IDEyMC43ODQgNzguOTY1OCAxMjAuNTQgNzkuMjg4MUMxMjAuMzA0IDc5LjYxMDQgMTIwLjEyMSA3OS45ODI3IDExOS45OTIgODAuNDA1M0MxMTkuODYzIDgwLjgyNzggMTE5Ljc5OSA4MS4yNzkgMTE5Ljc5OSA4MS43NTg4Wk0xMzcuMjc5IDg1LjU5MzhWNzEuNUgxMzkuODc5Vjg4SDEzNy41MjZMMTM3LjI3OSA4NS41OTM4Wk0xMjkuNzE3IDgyLjMxNzRWODIuMDkxOEMxMjkuNzE3IDgxLjIxMDkgMTI5LjgyMSA4MC40MDg5IDEzMC4wMjggNzkuNjg1NUMxMzAuMjM2IDc4Ljk1NTEgMTMwLjUzNyA3OC4zMjg1IDEzMC45MzEgNzcuODA1N0MxMzEuMzI1IDc3LjI3NTcgMTMxLjgwNCA3Ni44NzExIDEzMi4zNyA3Ni41OTE4QzEzMi45MzYgNzYuMzA1MyAxMzMuNTczIDc2LjE2MjEgMTM0LjI4MiA3Ni4xNjIxQzEzNC45ODQgNzYuMTYyMSAxMzUuNiA3Ni4yOTgyIDEzNi4xMyA3Ni41NzAzQzEzNi42NiA3Ni44NDI0IDEzNy4xMTEgNzcuMjMyNyAxMzcuNDgzIDc3Ljc0MTJDMTM3Ljg1NiA3OC4yNDI1IDEzOC4xNTMgNzguODQ0MSAxMzguMzc1IDc5LjU0NTlDMTM4LjU5NyA4MC4yNDA2IDEzOC43NTUgODEuMDE0IDEzOC44NDggODEuODY2MlY4Mi41ODU5QzEzOC43NTUgODMuNDE2NyAxMzguNTk3IDg0LjE3NTggMTM4LjM3NSA4NC44NjMzQzEzOC4xNTMgODUuNTUwOCAxMzcuODU2IDg2LjE0NTIgMTM3LjQ4MyA4Ni42NDY1QzEzNy4xMTEgODcuMTQ3OCAxMzYuNjU2IDg3LjUzNDUgMTM2LjExOSA4Ny44MDY2QzEzNS41ODkgODguMDc4OCAxMzQuOTcgODguMjE0OCAxMzQuMjYxIDg4LjIxNDhDMTMzLjU1OSA4OC4yMTQ4IDEzMi45MjUgODguMDY4IDEzMi4zNTkgODcuNzc0NEMxMzEuODAxIDg3LjQ4MDggMTMxLjMyNSA4Ny4wNjkgMTMwLjkzMSA4Ni41MzkxQzEzMC41MzcgODYuMDA5MSAxMzAuMjM2IDg1LjM4NjEgMTMwLjAyOCA4NC42Njk5QzEyOS44MjEgODMuOTQ2NiAxMjkuNzE3IDgzLjE2MjQgMTI5LjcxNyA4Mi4zMTc0Wk0xMzIuMzA2IDgyLjA5MThWODIuMzE3NEMxMzIuMzA2IDgyLjg0NzMgMTMyLjM1MiA4My4zNDE1IDEzMi40NDUgODMuNzk5OEMxMzIuNTQ2IDg0LjI1ODEgMTMyLjcgODQuNjYyOCAxMzIuOTA3IDg1LjAxMzdDMTMzLjExNSA4NS4zNTc0IDEzMy4zODMgODUuNjI5NiAxMzMuNzEzIDg1LjgzMDFDMTM0LjA0OSA4Ni4wMjM0IDEzNC40NTEgODYuMTIwMSAxMzQuOTE2IDg2LjEyMDFDMTM1LjUwMyA4Ni4xMjAxIDEzNS45ODcgODUuOTkxMiAxMzYuMzY2IDg1LjczMzRDMTM2Ljc0NiA4NS40NzU2IDEzNy4wNDMgODUuMTI4MyAxMzcuMjU4IDg0LjY5MTRDMTM3LjQ4IDg0LjI0NzQgMTM3LjYzIDgzLjc1MzMgMTM3LjcwOSA4My4yMDlWODEuMjY0NkMxMzcuNjY2IDgwLjg0MjEgMTM3LjU3NiA4MC40NDgyIDEzNy40NCA4MC4wODNDMTM3LjMxMiA3OS43MTc4IDEzNy4xMzYgNzkuMzk5MSAxMzYuOTE0IDc5LjEyN0MxMzYuNjkyIDc4Ljg0NzcgMTM2LjQxNiA3OC42MzI4IDEzNi4wODcgNzguNDgyNEMxMzUuNzY1IDc4LjMyNDkgMTM1LjM4MiA3OC4yNDYxIDEzNC45MzggNzguMjQ2MUMxMzQuNDY1IDc4LjI0NjEgMTM0LjA2NCA3OC4zNDY0IDEzMy43MzQgNzguNTQ2OUMxMzMuNDA1IDc4Ljc0NzQgMTMzLjEzMyA3OS4wMjMxIDEzMi45MTggNzkuMzc0QzEzMi43MSA3OS43MjQ5IDEzMi41NTYgODAuMTMzMSAxMzIuNDU2IDgwLjU5ODZDMTMyLjM1NiA4MS4wNjQxIDEzMi4zMDYgODEuNTYxOCAxMzIuMzA2IDgyLjA5MThaIiBmaWxsPSIjM0Y1MkREIi8+Cjwvc3ZnPgo=", - "description": "Sends the command to the device or updates attribute/time-series when the user clicks the button. Widget settings will enable you to configure behavior how to fetch the initial state and what to trigger when click.", + "description": "Allows single-click commands to devices or updates to attributes/time-series. Settings enable definition of the on-click action and condition when the button is disabled. Supports multiple layouts and custom styles for different states.", "descriptor": { "type": "rpc", "sizeX": 3, @@ -31,6 +31,9 @@ "subroutine call", "inter-process communication", "server request", - "button" + "button", + "update attribute", + "set attribute", + "add time-series" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/single_switch.json b/application/src/main/data/json/system/widget_types/single_switch.json index b14cf633a2..8faea8ba93 100644 --- a/application/src/main/data/json/system/widget_types/single_switch.json +++ b/application/src/main/data/json/system/widget_types/single_switch.json @@ -3,7 +3,7 @@ "name": "Single Switch", "deprecated": false, "image": "tb-image:c2luZ2xlLXN3aXRjaC5zdmc=:IlNpbmdsZSBTd2l0Y2giIHN5c3RlbSB3aWRnZXQgaW1hZ2U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMzEuNTc3MSIgeT0iNjUuMDc0MiIgd2lkdGg9IjU2Ljk5MDEiIGhlaWdodD0iMjkuODUyIiByeD0iMTQuOTI2IiBmaWxsPSIjNTQ2OUZGIi8+CjxjaXJjbGUgY3g9IjczLjY0MDkiIGN5PSI4MC4wMDAzIiByPSIxMi4yMTIyIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTA5LjI0MSA4My40NzE3QzEwOS4yNDEgODMuMTQ5NCAxMDkuMTkxIDgyLjg2MyAxMDkuMDkxIDgyLjYxMjNDMTA4Ljk5OCA4Mi4zNjE3IDEwOC44MjkgODIuMTMyNSAxMDguNTg2IDgxLjkyNDhDMTA4LjM0MiA4MS43MTcxIDEwNy45OTkgODEuNTE2NiAxMDcuNTU1IDgxLjMyMzJDMTA3LjExOCA4MS4xMjI3IDEwNi41NTkgODAuOTE4NiAxMDUuODc5IDgwLjcxMDlDMTA1LjEzNCA4MC40ODE4IDEwNC40NDcgODAuMjI3NSAxMDMuODE2IDc5Ljk0ODJDMTAzLjE5MyA3OS42NjE4IDEwMi42NDkgNzkuMzMyNCAxMDIuMTg0IDc4Ljk2QzEwMS43MTggNzguNTgwNCAxMDEuMzU2IDc4LjE0NzEgMTAxLjA5OSA3Ny42NjAyQzEwMC44NDEgNzcuMTY2IDEwMC43MTIgNzYuNTk2NyAxMDAuNzEyIDc1Ljk1MjFDMTAwLjcxMiA3NS4zMTQ4IDEwMC44NDQgNzQuNzM0NyAxMDEuMTA5IDc0LjIxMTlDMTAxLjM4MiA3My42ODkxIDEwMS43NjUgNzMuMjM4IDEwMi4yNTkgNzIuODU4NEMxMDIuNzYgNzIuNDcxNyAxMDMuMzUxIDcyLjE3NDUgMTA0LjAzMSA3MS45NjY4QzEwNC43MTIgNzEuNzUyIDEwNS40NjQgNzEuNjQ0NSAxMDYuMjg3IDcxLjY0NDVDMTA3LjQ0NyA3MS42NDQ1IDEwOC40NDYgNzEuODU5NCAxMDkuMjg0IDcyLjI4OTFDMTEwLjEyOSA3Mi43MTg4IDExMC43NzcgNzMuMjk1MiAxMTEuMjI5IDc0LjAxODZDMTExLjY4NyA3NC43NDE5IDExMS45MTYgNzUuNTQwNCAxMTEuOTE2IDc2LjQxNDFIMTA5LjI0MUMxMDkuMjQxIDc1Ljg5ODQgMTA5LjEzIDc1LjQ0MzcgMTA4LjkwOCA3NS4wNDk4QzEwOC42OTMgNzQuNjQ4OCAxMDguMzY0IDc0LjMzMzcgMTA3LjkyIDc0LjEwNDVDMTA3LjQ4MyA3My44NzUzIDEwNi45MjggNzMuNzYwNyAxMDYuMjU1IDczLjc2MDdDMTA1LjYxOCA3My43NjA3IDEwNS4wODggNzMuODU3NCAxMDQuNjY1IDc0LjA1MDhDMTA0LjI0MyA3NC4yNDQxIDEwMy45MjcgNzQuNTA1NSAxMDMuNzIgNzQuODM1QzEwMy41MTIgNzUuMTY0NCAxMDMuNDA4IDc1LjUzNjggMTAzLjQwOCA3NS45NTIxQzEwMy40MDggNzYuMjQ1OCAxMDMuNDc2IDc2LjUxNDMgMTAzLjYxMiA3Ni43NTc4QzEwMy43NDggNzYuOTk0MSAxMDMuOTU2IDc3LjIxNjEgMTA0LjIzNSA3Ny40MjM4QzEwNC41MTUgNzcuNjI0MyAxMDQuODY2IDc3LjgxNDEgMTA1LjI4OCA3Ny45OTMyQzEwNS43MTEgNzguMTcyMiAxMDYuMjA4IDc4LjM0NDEgMTA2Ljc4MSA3OC41MDg4QzEwNy42NDggNzguNzY2NiAxMDguNDAzIDc5LjA1MzEgMTA5LjA0OCA3OS4zNjgyQzEwOS42OTIgNzkuNjc2MSAxMTAuMjI5IDgwLjAyNyAxMTAuNjU5IDgwLjQyMDlDMTExLjA4OSA4MC44MTQ4IDExMS40MTEgODEuMjYyNCAxMTEuNjI2IDgxLjc2MzdDMTExLjg0MSA4Mi4yNTc4IDExMS45NDggODIuODIgMTExLjk0OCA4My40NTAyQzExMS45NDggODQuMTA5IDExMS44MTYgODQuNzAzNSAxMTEuNTUxIDg1LjIzMzRDMTExLjI4NiA4NS43NTYyIDExMC45MDYgODYuMjAzOCAxMTAuNDEyIDg2LjU3NjJDMTA5LjkyNSA4Ni45NDE0IDEwOS4zMzggODcuMjI0MyAxMDguNjUgODcuNDI0OEMxMDcuOTcgODcuNjE4MiAxMDcuMjExIDg3LjcxNDggMTA2LjM3MyA4Ny43MTQ4QzEwNS42MjEgODcuNzE0OCAxMDQuODggODcuNjE0NiAxMDQuMTQ5IDg3LjQxNDFDMTAzLjQyNiA4Ny4yMTM1IDEwMi43NjcgODYuOTA5MiAxMDIuMTczIDg2LjUwMUMxMDEuNTc4IDg2LjA4NTYgMTAxLjEwNiA4NS41NyAxMDAuNzU1IDg0Ljk1NDFDMTAwLjQwNCA4NC4zMzExIDEwMC4yMjkgODMuNjA0MiAxMDAuMjI5IDgyLjc3MzRIMTAyLjkyNUMxMDIuOTI1IDgzLjI4MTkgMTAzLjAxMSA4My43MTUyIDEwMy4xODMgODQuMDczMkMxMDMuMzYyIDg0LjQzMTMgMTAzLjYwOSA4NC43MjQ5IDEwMy45MjQgODQuOTU0MUMxMDQuMjM5IDg1LjE3NjEgMTA0LjYwNCA4NS4zNDA4IDEwNS4wMiA4NS40NDgyQzEwNS40NDIgODUuNTU1NyAxMDUuODkzIDg1LjYwOTQgMTA2LjM3MyA4NS42MDk0QzEwNy4wMDMgODUuNjA5NCAxMDcuNTMgODUuNTE5OSAxMDcuOTUyIDg1LjM0MDhDMTA4LjM4MiA4NS4xNjE4IDEwOC43MDQgODQuOTExMSAxMDguOTE5IDg0LjU4ODlDMTA5LjEzNCA4NC4yNjY2IDEwOS4yNDEgODMuODk0MiAxMDkuMjQxIDgzLjQ3MTdaTTExNy41NzcgODQuOTIxOUwxMjAuMjYzIDc1Ljg3N0gxMjEuOTE3TDEyMS40NjYgNzguNTg0TDExOC43NTkgODcuNUgxMTcuMjc2TDExNy41NzcgODQuOTIxOVpNMTE1Ljk5OCA3NS44NzdMMTE4LjA5MyA4NC45NjQ4TDExOC4yNjUgODcuNUgxMTYuNjFMMTEzLjQ2MyA3NS44NzdIMTE1Ljk5OFpNMTI0LjQzMSA4NC44NTc0TDEyNi40NjEgNzUuODc3SDEyOC45ODVMMTI1Ljg0OSA4Ny41SDEyNC4xOTRMMTI0LjQzMSA4NC44NTc0Wk0xMjIuMTk2IDc1Ljg3N0wxMjQuODUgODQuODE0NUwxMjUuMTgzIDg3LjVIMTIzLjdMMTIwLjk2MSA3OC41NzMyTDEyMC41MSA3NS44NzdIMTIyLjE5NlpNMTMzLjg2MiA3NS44NzdWODcuNUgxMzEuMjYzVjc1Ljg3N0gxMzMuODYyWk0xMzEuMDkxIDcyLjgyNjJDMTMxLjA5MSA3Mi40MzIzIDEzMS4yMiA3Mi4xMDY0IDEzMS40NzggNzEuODQ4NkMxMzEuNzQzIDcxLjU4MzcgMTMyLjEwOCA3MS40NTEyIDEzMi41NzMgNzEuNDUxMkMxMzMuMDMyIDcxLjQ1MTIgMTMzLjM5MyA3MS41ODM3IDEzMy42NTggNzEuODQ4NkMxMzMuOTIzIDcyLjEwNjQgMTM0LjA1NiA3Mi40MzIzIDEzNC4wNTYgNzIuODI2MkMxMzQuMDU2IDczLjIxMjkgMTMzLjkyMyA3My41MzUyIDEzMy42NTggNzMuNzkzQzEzMy4zOTMgNzQuMDUwOCAxMzMuMDMyIDc0LjE3OTcgMTMyLjU3MyA3NC4xNzk3QzEzMi4xMDggNzQuMTc5NyAxMzEuNzQzIDc0LjA1MDggMTMxLjQ3OCA3My43OTNDMTMxLjIyIDczLjUzNTIgMTMxLjA5MSA3My4yMTI5IDEzMS4wOTEgNzIuODI2MlpNMTQyLjM3IDc1Ljg3N1Y3Ny43Njc2SDEzNS44MTdWNzUuODc3SDE0Mi4zN1pNMTM3LjcwOCA3My4wMzAzSDE0MC4yOTdWODQuMjg4MUMxNDAuMjk3IDg0LjY0NjIgMTQwLjM0NyA4NC45MjE5IDE0MC40NDcgODUuMTE1MkMxNDAuNTU1IDg1LjMwMTQgMTQwLjcwMSA4NS40MjY4IDE0MC44ODggODUuNDkxMkMxNDEuMDc0IDg1LjU1NTcgMTQxLjI5MiA4NS41ODc5IDE0MS41NDMgODUuNTg3OUMxNDEuNzIyIDg1LjU4NzkgMTQxLjg5NCA4NS41NzcxIDE0Mi4wNTkgODUuNTU1N0MxNDIuMjIzIDg1LjUzNDIgMTQyLjM1NiA4NS41MTI3IDE0Mi40NTYgODUuNDkxMkwxNDIuNDY3IDg3LjQ2NzhDMTQyLjI1MiA4Ny41MzIyIDE0Mi4wMDEgODcuNTg5NSAxNDEuNzE1IDg3LjYzOTZDMTQxLjQzNiA4Ny42ODk4IDE0MS4xMTMgODcuNzE0OCAxNDAuNzQ4IDg3LjcxNDhDMTQwLjE1NCA4Ny43MTQ4IDEzOS42MjcgODcuNjExIDEzOS4xNjkgODcuNDAzM0MxMzguNzExIDg3LjE4ODUgMTM4LjM1MyA4Ni44NDExIDEzOC4wOTUgODYuMzYxM0MxMzcuODM3IDg1Ljg4MTUgMTM3LjcwOCA4NS4yNDQxIDEzNy43MDggODQuNDQ5MlY3My4wMzAzWk0xNDkuNDYgODUuNjUyM0MxNDkuODgyIDg1LjY1MjMgMTUwLjI2MiA4NS41NyAxNTAuNTk5IDg1LjQwNTNDMTUwLjk0MiA4NS4yMzM0IDE1MS4yMTggODQuOTk3MSAxNTEuNDI2IDg0LjY5NjNDMTUxLjY0MSA4NC4zOTU1IDE1MS43NTkgODQuMDQ4MiAxNTEuNzggODMuNjU0M0gxNTQuMjE5QzE1NC4yMDQgODQuNDA2MiAxNTMuOTgyIDg1LjA5MDIgMTUzLjU1MyA4NS43MDYxQzE1My4xMjMgODYuMzIxOSAxNTIuNTU0IDg2LjgxMjUgMTUxLjg0NSA4Ny4xNzc3QzE1MS4xMzYgODcuNTM1OCAxNTAuMzUyIDg3LjcxNDggMTQ5LjQ5MiA4Ny43MTQ4QzE0OC42MDQgODcuNzE0OCAxNDcuODMxIDg3LjU2NDUgMTQ3LjE3MiA4Ny4yNjM3QzE0Ni41MTMgODYuOTU1NyAxNDUuOTY1IDg2LjUzMzIgMTQ1LjUyOCA4NS45OTYxQzE0NS4wOTEgODUuNDU5IDE0NC43NjIgODQuODM5NSAxNDQuNTQgODQuMTM3N0MxNDQuMzI1IDgzLjQzNTkgMTQ0LjIxOCA4Mi42ODM5IDE0NC4yMTggODEuODgxOFY4MS41MDU5QzE0NC4yMTggODAuNzAzOCAxNDQuMzI1IDc5Ljk1MTggMTQ0LjU0IDc5LjI1QzE0NC43NjIgNzguNTQxIDE0NS4wOTEgNzcuOTE4IDE0NS41MjggNzcuMzgwOUMxNDUuOTY1IDc2Ljg0MzggMTQ2LjUxMyA3Ni40MjQ4IDE0Ny4xNzIgNzYuMTI0QzE0Ny44MzEgNzUuODE2MSAxNDguNjAxIDc1LjY2MjEgMTQ5LjQ4MSA3NS42NjIxQzE1MC40MTIgNzUuNjYyMSAxNTEuMjI5IDc1Ljg0ODMgMTUxLjkzMSA3Ni4yMjA3QzE1Mi42MzIgNzYuNTg1OSAxNTMuMTg0IDc3LjA5OCAxNTMuNTg1IDc3Ljc1NjhDMTUzLjk5MyA3OC40MDg1IDE1NC4yMDQgNzkuMTY3NiAxNTQuMjE5IDgwLjAzNDJIMTUxLjc4QzE1MS43NTkgNzkuNjA0NSAxNTEuNjUxIDc5LjIxNzggMTUxLjQ1OCA3OC44NzRDMTUxLjI3MiA3OC41MjMxIDE1MS4wMDcgNzguMjQzOCAxNTAuNjYzIDc4LjAzNjFDMTUwLjMyNiA3Ny44Mjg1IDE0OS45MjIgNzcuNzI0NiAxNDkuNDQ5IDc3LjcyNDZDMTQ4LjkyNiA3Ny43MjQ2IDE0OC40OTMgNzcuODMyIDE0OC4xNDkgNzguMDQ2OUMxNDcuODA2IDc4LjI1NDYgMTQ3LjUzNyA3OC41NDEgMTQ3LjM0NCA3OC45MDYyQzE0Ny4xNSA3OS4yNjQzIDE0Ny4wMTEgNzkuNjY4OSAxNDYuOTI1IDgwLjEyMDFDMTQ2Ljg0NiA4MC41NjQxIDE0Ni44MDcgODEuMDI2IDE0Ni44MDcgODEuNTA1OVY4MS44ODE4QzE0Ni44MDcgODIuMzYxNyAxNDYuODQ2IDgyLjgyNzEgMTQ2LjkyNSA4My4yNzgzQzE0Ny4wMDQgODMuNzI5NSAxNDcuMTQgODQuMTM0MSAxNDcuMzMzIDg0LjQ5MjJDMTQ3LjUzNCA4NC44NDMxIDE0Ny44MDYgODUuMTI2IDE0OC4xNDkgODUuMzQwOEMxNDguNDkzIDg1LjU0ODUgMTQ4LjkzIDg1LjY1MjMgMTQ5LjQ2IDg1LjY1MjNaTTE1OS4xMDYgNzFWODcuNUgxNTYuNTI4VjcxSDE1OS4xMDZaTTE1OC42NTUgODEuMjU4OEwxNTcuODE3IDgxLjI0OEMxNTcuODI1IDgwLjQ0NiAxNTcuOTM2IDc5LjcwNDggMTU4LjE1IDc5LjAyNDRDMTU4LjM3MiA3OC4zNDQxIDE1OC42OCA3Ny43NTMzIDE1OS4wNzQgNzcuMjUyQzE1OS40NzUgNzYuNzQzNSAxNTkuOTU1IDc2LjM1MzIgMTYwLjUxNCA3Ni4wODExQzE2MS4wNzIgNzUuODAxOCAxNjEuNjkyIDc1LjY2MjEgMTYyLjM3MiA3NS42NjIxQzE2Mi45NDUgNzUuNjYyMSAxNjMuNDYxIDc1Ljc0MDkgMTYzLjkxOSA3NS44OTg0QzE2NC4zODQgNzYuMDU2IDE2NC43ODUgNzYuMzEwMiAxNjUuMTIyIDc2LjY2MTFDMTY1LjQ1OSA3Ny4wMDQ5IDE2NS43MTMgNzcuNDU2MSAxNjUuODg1IDc4LjAxNDZDMTY2LjA2NCA3OC41NjYxIDE2Ni4xNTMgNzkuMjM5MyAxNjYuMTUzIDgwLjAzNDJWODcuNUgxNjMuNTU0VjgwLjAxMjdDMTYzLjU1NCA3OS40NTQxIDE2My40NzEgNzkuMDEwMSAxNjMuMzA3IDc4LjY4MDdDMTYzLjE0OSA3OC4zNTEyIDE2Mi45MTYgNzguMTE0OSAxNjIuNjA4IDc3Ljk3MTdDMTYyLjMgNzcuODIxMyAxNjEuOTI0IDc3Ljc0NjEgMTYxLjQ4IDc3Ljc0NjFDMTYxLjAxNSA3Ny43NDYxIDE2MC42MDMgNzcuODM5MiAxNjAuMjQ1IDc4LjAyNTRDMTU5Ljg5NCA3OC4yMTE2IDE1OS42MDEgNzguNDY1OCAxNTkuMzY0IDc4Ljc4ODFDMTU5LjEyOCA3OS4xMTA0IDE1OC45NDkgNzkuNDgyNyAxNTguODI3IDc5LjkwNTNDMTU4LjcxMyA4MC4zMjc4IDE1OC42NTUgODAuNzc5IDE1OC42NTUgODEuMjU4OFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPC9zdmc+Cg==", - "description": "Sends the command to the device or updates attribute/time-series when the user toggles the slider. Widget settings will enable you to configure behavior how to fetch the initial state and what to trigger when turn on/off states.", + "description": "Allows users to toggle a slider to send commands to devices or update attributes/time-series data. Configurable settings let users define how to retrieve the initial state and specify actions for the on/off toggle.", "descriptor": { "type": "rpc", "sizeX": 3.5, @@ -30,6 +30,9 @@ "interface", "subroutine call", "inter-process communication", - "server request" + "server request", + "update attribute", + "set attribute", + "add time-series" ] } \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4160b50024..92e4ab9895 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5185,7 +5185,7 @@ "action-button": { "behavior": "Behavior", "on-click": "On click", - "on-click-hint": "Action performed when the button is clicked." + "on-click-hint": "Action triggered when the button is clicked" }, "command-button": { "behavior": "Behavior", @@ -5214,9 +5214,9 @@ }, "button-state": { "activated-state": "Activated state", - "activated-state-hint": "Condition under which the button is active.", + "activated-state-hint": "Configure condition under which the button is active.", "disabled-state": "Disabled state", - "disabled-state-hint": "Condition under which the button is disabled.", + "disabled-state-hint": "Configure condition under which the button is disabled.", "enabled": "Enabled", "hovered": "Hovered", "pressed": "Pressed", @@ -6526,13 +6526,13 @@ }, "rpc-state": { "initial-state": "Initial state", - "initial-state-hint": "Action to get the initial value of the component.", + "initial-state-hint": "Action to get the initial state (On/Off) of the component.", "disabled-state": "Disabled state", - "disabled-state-hint": "Condition under which the component is disabled.", + "disabled-state-hint": "Configure condition under which the component is disabled.", "turn-on": "Turn 'On'", - "turn-on-hint": "Action performed to turn ON the component.", + "turn-on-hint": "Action triggered when the slider is switched to 'On'", "turn-off": "Turn 'Off'", - "turn-off-hint": "Action performed to turn OFF the component.", + "turn-off-hint": "Action triggered when the slider is switched to 'Off'", "on": "On", "off": "Off", "disabled": "Disabled" From 7cd4d477ff98d66072ebca833afda3b596f9a0d5 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 8 Feb 2024 15:53:55 +0200 Subject: [PATCH 110/137] Fix edge cycling: update edge root rule chain, add test. Improve Edge rpc request to edge device. Add support for originator fields for rule nodes --- .../device/DeviceActorMessageProcessor.java | 15 ++-- .../RuleChainActorMessageProcessor.java | 2 +- .../service/edge/rpc/EdgeGrpcService.java | 2 + .../rule/RuleChainEdgeProcessor.java | 5 ++ .../service/queue/TbMsgPackCallback.java | 1 - .../server/edge/RuleChainEdgeTest.java | 23 ++++- .../RuleChainMsgConstructorTest.java | 87 +++++++++---------- .../notification/DefaultNotifications.java | 4 +- .../thingsboard/rest/client/RestClient.java | 5 +- .../util/EntitiesFieldsAsyncLoader.java | 4 + 10 files changed, 89 insertions(+), 59 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 58bd82abb3..9f92838712 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -66,7 +66,6 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNoti import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; @@ -90,7 +89,9 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; +import org.thingsboard.server.gen.transport.TransportProtos.UplinkNotificationMsg; import org.thingsboard.server.service.rpc.RpcSubmitStrategy; +import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import javax.annotation.Nullable; @@ -173,7 +174,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso private EdgeId findRelatedEdgeId() { List result = - systemContext.getRelationService().findByToAndType(tenantId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON); + systemContext.getRelationService().findByToAndType(tenantId, deviceId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); if (result != null && result.size() > 0) { EntityRelation relationToEdge = result.get(0); if (relationToEdge.getFrom() != null && relationToEdge.getFrom().getId() != null) { @@ -212,8 +213,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso if (systemContext.isEdgesEnabled() && edgeId != null) { log.debug("[{}][{}] device is related to edge: [{}]. Saving RPC request: [{}][{}] to edge queue", tenantId, deviceId, edgeId.getId(), rpcId, requestId); try { - saveRpcRequestToEdgeQueue(request, requestId).get(); - sent = true; + Optional edgeAttributeOpt = systemContext.getAttributesService().find(tenantId, edgeId, DataConstants.SERVER_SCOPE, DefaultDeviceStateService.ACTIVITY_STATE).get(); + if (edgeAttributeOpt.isPresent() && edgeAttributeOpt.get().getBooleanValue().orElse(false)) { + saveRpcRequestToEdgeQueue(request, requestId).get(); + } else { + log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}. The Edge is currently offline or unreachable", tenantId, deviceId, edgeId.getId(), request); + } } catch (InterruptedException | ExecutionException e) { log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}", tenantId, deviceId, edgeId.getId(), request, e); } @@ -470,7 +475,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso callback.onSuccess(); } - private void processUplinkNotificationMsg(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg uplinkNotificationMsg) { + private void processUplinkNotificationMsg(SessionInfoProto sessionInfo, UplinkNotificationMsg uplinkNotificationMsg) { String nodeId = sessionInfo.getNodeId(); sessions.entrySet().stream() .filter(kv -> kv.getValue().getSessionInfo().getNodeId().equals(nodeId) && (kv.getValue().isSubscribedToAttributes() || kv.getValue().isSubscribedToRPC())) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index e14ed8203b..870ccda60a 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -16,7 +16,6 @@ package org.thingsboard.server.actors.ruleChain; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; @@ -29,6 +28,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.relation.EntityRelation; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index c23fd98d8e..026ca51e8d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -477,6 +477,8 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i TbMsgMetaData md = new TbMsgMetaData(); if (!persistToTelemetry) { md.putValue(DataConstants.SCOPE, DataConstants.SERVER_SCOPE); + md.putValue("edgeName", edge.getName()); + md.putValue("edgeType", edge.getType()); } TbMsg tbMsg = TbMsg.newMsg(msgType, edgeId, md, TbMsgDataType.JSON, data); clusterService.pushMsgToRuleEngine(tenantId, edgeId, tbMsg, null); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java index 2a191dfaf4..118cc0ccd4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.processor.rule; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.rule.RuleChain; @@ -53,6 +54,10 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { isRoot = Boolean.parseBoolean(edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY).asText()); } catch (Exception ignored) {} } + if (!isRoot) { + Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeEvent.getEdgeId()); + isRoot = edge.getRootRuleChainId().equals(ruleChainId); + } UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RuleChainUpdateMsg ruleChainUpdateMsg = ((RuleChainMsgConstructor) ruleChainMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java index d7ddd5d81b..1f9e85bdc7 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.RuleNodeInfo; import org.thingsboard.server.common.msg.queue.TbMsgCallback; -import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import java.util.UUID; import java.util.concurrent.TimeUnit; diff --git a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java index 4942dbdb9b..62d2c5eade 100644 --- a/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/RuleChainEdgeTest.java @@ -146,7 +146,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { } } - private void createRuleChainMetadata(RuleChain ruleChain) { + private RuleChainMetaData createRuleChainMetadata(RuleChain ruleChain) { RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); ruleChainMetaData.setRuleChainId(ruleChain.getId()); @@ -182,7 +182,7 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { ruleChainMetaData.addConnectionInfo(0, 2, "fail"); ruleChainMetaData.addConnectionInfo(1, 2, "success"); - doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); + return doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); } @Test @@ -193,9 +193,10 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { ruleChain.setType(RuleChainType.EDGE); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); - edgeImitator.expectMessageAmount(1); + edgeImitator.expectMessageAmount(2); doPost("/api/edge/" + edge.getUuidId() + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); + RuleChainMetaData metaData = createRuleChainMetadata(savedRuleChain); Assert.assertTrue(edgeImitator.waitForMessages()); // set new rule chain as root @@ -213,6 +214,22 @@ public class RuleChainEdgeTest extends AbstractEdgeTest { Assert.assertTrue(ruleChainMsg.isRoot()); Assert.assertEquals(savedRuleChain.getId(), ruleChainMsg.getId()); + // update metadata for root rule chain + edgeImitator.expectMessageAmount(1); + metaData.getNodes().forEach(n -> n.setDebugMode(true)); + doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); + Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); + ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); + ruleChainMsg = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true); + Assert.assertNotNull(ruleChainMsg); + Assert.assertTrue(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType()) || + UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType())); + Assert.assertEquals(savedRuleChain.getId(), ruleChainMsg.getId()); + Assert.assertEquals(savedRuleChain.getName(), ruleChainMsg.getName()); + Assert.assertTrue(ruleChainMsg.isRoot()); + // revert root rule chain edgeImitator.expectMessageAmount(1); doPost("/api/edge/" + edge.getUuidId() diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java index ce6ca753b3..d6bfbf1036 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge.rpc.constructor; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; @@ -61,7 +60,7 @@ public class RuleChainMsgConstructorTest { } @Test - public void testConstructRuleChainMetadataUpdatedMsg_V_3_4_0() throws JsonProcessingException { + public void testConstructRuleChainMetadataUpdatedMsg_V_3_4_0() { RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); RuleChainMetaData ruleChainMetaData = createRuleChainMetaData( ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); @@ -80,7 +79,7 @@ public class RuleChainMsgConstructorTest { } @Test - public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_3() throws JsonProcessingException { + public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_3() { RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); RuleChainMetaData ruleChainMetaData = createRuleChainMetaData( ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); @@ -120,7 +119,7 @@ public class RuleChainMsgConstructorTest { } @Test - public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0() throws JsonProcessingException { + public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0() { RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); RuleChainMetaData ruleChainMetaData = createRuleChainMetaData(ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = @@ -161,7 +160,7 @@ public class RuleChainMsgConstructorTest { } @Test - public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0_inDifferentOrder() throws JsonProcessingException { + public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0_inDifferentOrder() { // same rule chain metadata, but different order of rule nodes RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); RuleChainMetaData ruleChainMetaData1 = createRuleChainMetaData(ruleChainId, 8, createRuleNodesInDifferentOrder(ruleChainId), createConnectionsInDifferentOrder()); @@ -254,7 +253,7 @@ public class RuleChainMsgConstructorTest { return result; } - private List createRuleNodes(RuleChainId ruleChainId) throws JsonProcessingException { + private List createRuleNodes(RuleChainId ruleChainId) { List result = new ArrayList<>(); result.add(getOutputNode(ruleChainId)); result.add(getAcknowledgeNode(ruleChainId)); @@ -301,7 +300,7 @@ public class RuleChainMsgConstructorTest { return result; } - private List createRuleNodesInDifferentOrder(RuleChainId ruleChainId) throws JsonProcessingException { + private List createRuleNodesInDifferentOrder(RuleChainId ruleChainId) { List result = new ArrayList<>(); result.add(getPushToAnalyticsNode(ruleChainId)); result.add(getPushToCloudNode(ruleChainId)); @@ -319,99 +318,99 @@ public class RuleChainMsgConstructorTest { } - private RuleNode getOutputNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getOutputNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbRuleChainOutputNode", "Output node", - JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}")); + JacksonUtil.toJsonNode("{\"version\":0}"), + JacksonUtil.toJsonNode("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}")); } - private RuleNode getCheckpointNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getCheckpointNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbCheckpointNode", "Checkpoint node", - JacksonUtil.OBJECT_MAPPER.readTree("{\"queueName\":\"HighPriority\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}")); + JacksonUtil.toJsonNode("{\"queueName\":\"HighPriority\"}"), + JacksonUtil.toJsonNode("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}")); } - private RuleNode getSaveTimeSeriesNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getSaveTimeSeriesNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "Save Timeseries", - JacksonUtil.OBJECT_MAPPER.readTree("{\"defaultTTL\":0}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":823,\"layoutY\":157}")); + JacksonUtil.toJsonNode("{\"defaultTTL\":0}"), + JacksonUtil.toJsonNode("{\"layoutX\":823,\"layoutY\":157}")); } - private RuleNode getMessageTypeSwitchNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getMessageTypeSwitchNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "Message Type Switch", - JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":347,\"layoutY\":149}")); + JacksonUtil.toJsonNode("{\"version\":0}"), + JacksonUtil.toJsonNode("{\"layoutX\":347,\"layoutY\":149}")); } - private RuleNode getLogOtherNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getLogOtherNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.action.TbLogNode", "Log Other", - JacksonUtil.OBJECT_MAPPER.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":378}")); + JacksonUtil.toJsonNode("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), + JacksonUtil.toJsonNode("{\"layoutX\":824,\"layoutY\":378}")); } - private RuleNode getPushToCloudNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getPushToCloudNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "Push to cloud", - JacksonUtil.OBJECT_MAPPER.readTree("{\"scope\":\"SERVER_SCOPE\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":1129,\"layoutY\":52}")); + JacksonUtil.toJsonNode("{\"scope\":\"SERVER_SCOPE\"}"), + JacksonUtil.toJsonNode("{\"layoutX\":1129,\"layoutY\":52}")); } - private RuleNode getAcknowledgeNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getAcknowledgeNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbAckNode", "Acknowledge node", - JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}")); + JacksonUtil.toJsonNode("{\"version\":0}"), + JacksonUtil.toJsonNode("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}")); } - private RuleNode getDeviceProfileNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getDeviceProfileNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "Device Profile Node", - JacksonUtil.OBJECT_MAPPER.readTree("{\"persistAlarmRulesState\":false,\"fetchAlarmRulesStateOnStart\":false}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}")); + JacksonUtil.toJsonNode("{\"persistAlarmRulesState\":false,\"fetchAlarmRulesStateOnStart\":false}"), + JacksonUtil.toJsonNode("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}")); } - private RuleNode getSaveClientAttributesNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getSaveClientAttributesNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "Save Client Attributes", - JacksonUtil.OBJECT_MAPPER.readTree("{\"scope\":\"CLIENT_SCOPE\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":52}")); + JacksonUtil.toJsonNode("{\"scope\":\"CLIENT_SCOPE\"}"), + JacksonUtil.toJsonNode("{\"layoutX\":824,\"layoutY\":52}")); } - private RuleNode getLogRpcFromDeviceNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getLogRpcFromDeviceNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.action.TbLogNode", "Log RPC from Device", - JacksonUtil.OBJECT_MAPPER.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":825,\"layoutY\":266}")); + JacksonUtil.toJsonNode("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), + JacksonUtil.toJsonNode("{\"layoutX\":825,\"layoutY\":266}")); } - private RuleNode getRpcCallRequestNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getRpcCallRequestNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "RPC Call Request", - JacksonUtil.OBJECT_MAPPER.readTree("{\"timeoutInSeconds\":60}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":466}")); + JacksonUtil.toJsonNode("{\"timeoutInSeconds\":60}"), + JacksonUtil.toJsonNode("{\"layoutX\":824,\"layoutY\":466}")); } - private RuleNode getPushToAnalyticsNode(RuleChainId ruleChainId) throws JsonProcessingException { + private RuleNode getPushToAnalyticsNode(RuleChainId ruleChainId) { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbRuleChainInputNode", "Push to Analytics", - JacksonUtil.OBJECT_MAPPER.readTree("{\"ruleChainId\":\"af588000-6c7c-11ec-bafd-c9a47a5c8d99\"}"), - JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":477,\"layoutY\":560}")); + JacksonUtil.toJsonNode("{\"ruleChainId\":\"af588000-6c7c-11ec-bafd-c9a47a5c8d99\"}"), + JacksonUtil.toJsonNode("{\"description\":\"\",\"layoutX\":477,\"layoutY\":560}")); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 1ef0006e87..fd17618bd1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -40,9 +40,9 @@ import org.thingsboard.server.common.data.notification.rule.trigger.config.Alarm import org.thingsboard.server.common.data.notification.rule.trigger.config.ApiUsageLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent; +import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeConnectionNotificationRuleTriggerConfig.EdgeConnectivityEvent; -import org.thingsboard.server.common.data.notification.rule.trigger.config.EdgeCommunicationFailureNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NewPlatformVersionNotificationRuleTriggerConfig; @@ -347,7 +347,7 @@ public class DefaultNotifications { public static final DefaultNotification edgeCommunicationFailures = DefaultNotification.builder() .name("Edge communication failure notification") .type(NotificationType.EDGE_COMMUNICATION_FAILURE) - .subject("Edge '${edgeName}' communication failure occured") + .subject("Edge '${edgeName}' communication failure occurred") .text("Failure message: '${failureMsg}'") .icon("error").color(RED_COLOR) .button("Go to Edge").link("/edgeManagement/instances/${edgeId}") diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index f3a7ee77c7..8783770be3 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -525,12 +525,11 @@ public class RestClient implements Closeable { } public PageData getAlarmComments(AlarmId alarmId, PageLink pageLink) { - String urlSecondPart = "/api/alarm/{alarmId}/comment"; Map params = new HashMap<>(); params.put("alarmId", alarmId.getId().toString()); - + addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + urlSecondPart + "&" + getUrlParams(pageLink), + baseURL + "/api/alarm/{alarmId}/comment?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java index 80d73b9478..4d651f48fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -63,6 +64,9 @@ public class EntitiesFieldsAsyncLoader { case ENTITY_VIEW: return toEntityFieldsDataAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), (EntityViewId) originatorId), EntityFieldsData::new, ctx); + case EDGE: + return toEntityFieldsDataAsync(ctx.getEdgeService().findEdgeByIdAsync(ctx.getTenantId(), (EdgeId) originatorId), + EntityFieldsData::new, ctx); default: return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType: " + originatorId.getEntityType())); } From 841514384209e5a976e9b3c64d53cd1d99de4fb2 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 8 Feb 2024 16:51:49 +0200 Subject: [PATCH 111/137] Add TO_SERVER_RPC_REQUEST to AbstractTbMsgPushNode --- .../thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index 31fda250d0..4176ead31c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -47,6 +47,7 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.INACTIVITY_EVENT; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_REQUEST; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; import static org.thingsboard.server.common.data.msg.TbMsgType.TIMESERIES_UPDATED; +import static org.thingsboard.server.common.data.msg.TbMsgType.TO_SERVER_RPC_REQUEST; @Slf4j public abstract class AbstractTbMsgPushNode implements TbNode { @@ -176,6 +177,6 @@ public abstract class AbstractTbMsgPushNode Date: Thu, 8 Feb 2024 18:09:12 +0200 Subject: [PATCH 112/137] UI: added new open URL action type --- .../action/widget-action.component.html | 19 ++++++++++++++++++- .../common/action/widget-action.component.ts | 10 ++++++++++ .../components/widget/widget.component.ts | 3 +++ ui-ngx/src/app/shared/models/widget.models.ts | 7 +++++-- .../assets/locale/locale.constant-en_US.json | 3 +++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html index 35ca3f3144..3752fb3b3c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html @@ -80,6 +80,22 @@
    + +
    +
    {{ 'widget-action.URL' | translate }}
    + + + + warning + + +
    +
    @@ -89,7 +105,8 @@
    -
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts index c93a05fafd..6c2cd3fb8f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts @@ -279,6 +279,16 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali this.fb.control(action ? action.mobileAction : null, [Validators.required]) ); break; + case WidgetActionType.openURL: + this.actionTypeFormGroup.addControl( + 'openNewBrowserTab', + this.fb.control(action ? action.openNewBrowserTab : false, []) + ); + this.actionTypeFormGroup.addControl( + 'url', + this.fb.control(action ? action.url : null, [Validators.required]) + ); + break; } } this.actionTypeFormGroupSubscriptions.push( diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 15e982c060..a79fa89242 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -1089,6 +1089,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.router.navigateByUrl(url); } break; + case WidgetActionType.openURL: + window.open(descriptor.url, descriptor.openNewBrowserTab ? '_blank' : '_self'); + break; case WidgetActionType.custom: const customFunction = descriptor.customFunction; if (customFunction && customFunction.length > 0) { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 625d957cff..5f4ca8f7be 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -538,7 +538,8 @@ export enum WidgetActionType { openDashboard = 'openDashboard', custom = 'custom', customPretty = 'customPretty', - mobileAction = 'mobileAction' + mobileAction = 'mobileAction', + openURL = 'openURL' } export enum WidgetMobileActionType { @@ -559,7 +560,8 @@ export const widgetActionTypeTranslationMap = new Map( [ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ], [ WidgetActionType.custom, 'widget-action.custom' ], [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ], - [ WidgetActionType.mobileAction, 'widget-action.mobile-action' ] + [ WidgetActionType.mobileAction, 'widget-action.mobile-action' ], + [ WidgetActionType.openURL, 'widget-action.open-URL' ] ] ); @@ -671,6 +673,7 @@ export interface WidgetAction extends CustomActionDescriptor { setEntityId?: boolean; stateEntityParamName?: string; mobileAction?: WidgetMobileActionDescriptor; + url?: string; } export interface WidgetActionDescriptor extends WidgetAction { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index b3d2821e30..ee3d1d29d7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5036,6 +5036,9 @@ "popover-height": "Popover height", "popover-style": "Popover style", "open-new-browser-tab": "Open in a new browser tab", + "open-URL": "Open URL", + "URL": "URL", + "url-required": "URL is required.", "mobile": { "action-type": "Mobile action type", "select-action-type": "Select mobile action type", From 2faf63bf81c82a6e352015c82e311d7b36d12335 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Feb 2024 19:48:32 +0200 Subject: [PATCH 113/137] UI: Implement power button widget --- .../json/system/widget_bundles/buttons.json | 3 +- .../widget_bundles/control_widgets.json | 2 + .../system/widget_types/power_button.json | 36 + .../basic/basic-widget-config.module.ts | 12 +- .../power-button-basic-config.component.html | 197 ++++ .../power-button-basic-config.component.ts | 195 ++++ .../widget/lib/action/action-widget.models.ts | 2 + .../rpc/power-button-widget.component.html | 32 + .../rpc/power-button-widget.component.scss | 64 ++ .../lib/rpc/power-button-widget.component.ts | 200 ++++ .../lib/rpc/power-button-widget.models.ts | 912 ++++++++++++++++++ ...ower-button-widget-settings.component.html | 142 +++ .../power-button-widget-settings.component.ts | 88 ++ .../lib/settings/widget-settings.module.ts | 12 +- .../widget/widget-components.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 22 + .../widget/power-button/default-layout.svg | 22 + .../power-button/default-volume-layout.svg | 50 + .../widget/power-button/outlined-layout.svg | 23 + .../power-button/outlined-volume-layout.svg | 37 + .../widget/power-button/simplified-layout.svg | 20 + .../power-button/simplified-volume-layout.svg | 33 + 22 files changed, 2102 insertions(+), 9 deletions(-) create mode 100644 application/src/main/data/json/system/widget_types/power_button.json create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.ts create mode 100644 ui-ngx/src/assets/widget/power-button/default-layout.svg create mode 100644 ui-ngx/src/assets/widget/power-button/default-volume-layout.svg create mode 100644 ui-ngx/src/assets/widget/power-button/outlined-layout.svg create mode 100644 ui-ngx/src/assets/widget/power-button/outlined-volume-layout.svg create mode 100644 ui-ngx/src/assets/widget/power-button/simplified-layout.svg create mode 100644 ui-ngx/src/assets/widget/power-button/simplified-volume-layout.svg diff --git a/application/src/main/data/json/system/widget_bundles/buttons.json b/application/src/main/data/json/system/widget_bundles/buttons.json index a48fba892b..00036a0cba 100644 --- a/application/src/main/data/json/system/widget_bundles/buttons.json +++ b/application/src/main/data/json/system/widget_bundles/buttons.json @@ -9,6 +9,7 @@ }, "widgetTypeFqns": [ "action_button", - "command_button" + "command_button", + "power_button" ] } \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 5dce630e64..568a144874 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -9,6 +9,8 @@ }, "widgetTypeFqns": [ "single_switch", + "command_button", + "power_button", "control_widgets.switch_control", "control_widgets.slide_toggle_control", "control_widgets.round_switch", diff --git a/application/src/main/data/json/system/widget_types/power_button.json b/application/src/main/data/json/system/widget_types/power_button.json new file mode 100644 index 0000000000..2f825c9e74 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/power_button.json @@ -0,0 +1,36 @@ +{ + "fqn": "power_button", + "name": "Power button", + "deprecated": false, + "image": "tb-image:cG93ZXItYnV0dG9uLnN2Zw==:IlBvd2VyIGJ1dHRvbiIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2lfNDU4OF84ODQyMCkiPgo8cGF0aCBkPSJNMTQ2LjUgODBDMTQ2LjUgMTA1LjEyOSAxMjUuNjgxIDEyNS41IDEwMCAxMjUuNUM3NC4zMTg4IDEyNS41IDUzLjUgMTA1LjEyOSA1My41IDgwQzUzLjUgNTQuODcxIDc0LjMxODggMzQuNSAxMDAgMzQuNUMxMjUuNjgxIDM0LjUgMTQ2LjUgNTQuODcxIDE0Ni41IDgwWiIgZmlsbD0iIzNGNTJERCIvPgo8L2c+CjxwYXRoIGQ9Ik0xNDUuNSA4MEMxNDUuNSAxMDQuNTU2IDEyNS4xNSAxMjQuNSAxMDAgMTI0LjVDNzQuODUwNCAxMjQuNSA1NC41IDEwNC41NTYgNTQuNSA4MEM1NC41IDU1LjQ0MzcgNzQuODUwNCAzNS41IDEwMCAzNS41QzEyNS4xNSAzNS41IDE0NS41IDU1LjQ0MzcgMTQ1LjUgODBaIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjFfZF80NTg4Xzg4NDIwKSI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTAwIDEzNS41QzEzMS4yMDQgMTM1LjUgMTU2LjUgMTEwLjY1MiAxNTYuNSA4MEMxNTYuNSA0OS4zNDgyIDEzMS4yMDQgMjQuNSAxMDAgMjQuNUM2OC43OTU5IDI0LjUgNDMuNSA0OS4zNDgyIDQzLjUgODBDNDMuNSAxMTAuNjUyIDY4Ljc5NTkgMTM1LjUgMTAwIDEzNS41Wk0xMDAgMTI1LjVDMTI1LjY4MSAxMjUuNSAxNDYuNSAxMDUuMTI5IDE0Ni41IDgwQzE0Ni41IDU0Ljg3MSAxMjUuNjgxIDM0LjUgMTAwIDM0LjVDNzQuMzE4OCAzNC41IDUzLjUgNTQuODcxIDUzLjUgODBDNTMuNSAxMDUuMTI5IDc0LjMxODggMTI1LjUgMTAwIDEyNS41WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzQ1ODhfODg0MjApIi8+CjwvZz4KPHBhdGggZD0iTTk4LjM1NzQgNzkuMTg1NVY4MC4xNzM4Qzk4LjM1NzQgODEuMzQ4MyA5OC4yMTA2IDgyLjQwMSA5Ny45MTcgODMuMzMyQzk3LjYyMzQgODQuMjYzIDk3LjIwMDggODUuMDU0NCA5Ni42NDk0IDg1LjcwNjFDOTYuMDk4IDg2LjM1NzcgOTUuNDM1NSA4Ni44NTU1IDk0LjY2MjEgODcuMTk5MkM5My44OTU4IDg3LjU0MyA5My4wMzY1IDg3LjcxNDggOTIuMDg0IDg3LjcxNDhDOTEuMTYwMiA4Ny43MTQ4IDkwLjMxMTUgODcuNTQzIDg5LjUzODEgODcuMTk5MkM4OC43NzE4IDg2Ljg1NTUgODguMTA1OCA4Ni4zNTc3IDg3LjU0IDg1LjcwNjFDODYuOTgxNCA4NS4wNTQ0IDg2LjU0ODIgODQuMjYzIDg2LjI0MDIgODMuMzMyQzg1LjkzMjMgODIuNDAxIDg1Ljc3ODMgODEuMzQ4MyA4NS43NzgzIDgwLjE3MzhWNzkuMTg1NUM4NS43NzgzIDc4LjAxMTEgODUuOTI4NyA3Ni45NjE5IDg2LjIyOTUgNzYuMDM4MUM4Ni41Mzc0IDc1LjEwNzEgODYuOTcwNyA3NC4zMTU4IDg3LjUyOTMgNzMuNjY0MUM4OC4wODc5IDczLjAwNTIgODguNzUwMyA3Mi41MDM5IDg5LjUxNjYgNzIuMTYwMkM5MC4yOSA3MS44MTY0IDkxLjEzODcgNzEuNjQ0NSA5Mi4wNjI1IDcxLjY0NDVDOTMuMDE1IDcxLjY0NDUgOTMuODc0MyA3MS44MTY0IDk0LjY0MDYgNzIuMTYwMkM5NS40MTQxIDcyLjUwMzkgOTYuMDc2NSA3My4wMDUyIDk2LjYyNzkgNzMuNjY0MUM5Ny4xODY1IDc0LjMxNTggOTcuNjEyNiA3NS4xMDcxIDk3LjkwNjIgNzYuMDM4MUM5OC4yMDcgNzYuOTYxOSA5OC4zNTc0IDc4LjAxMTEgOTguMzU3NCA3OS4xODU1Wk05Ni4zMDU3IDgwLjE3MzhWNzkuMTY0MUM5Ni4zMDU3IDc4LjIzMzEgOTYuMjA5IDc3LjQwOTUgOTYuMDE1NiA3Ni42OTM0Qzk1LjgyOTQgNzUuOTc3MiA5NS41NTM3IDc1LjM3NTcgOTUuMTg4NSA3NC44ODg3Qzk0LjgyMzIgNzQuNDAxNyA5NC4zNzU3IDc0LjAzMjkgOTMuODQ1NyA3My43ODIyQzkzLjMyMjkgNzMuNTMxNiA5Mi43Mjg1IDczLjQwNjIgOTIuMDYyNSA3My40MDYyQzkxLjQxOCA3My40MDYyIDkwLjgzNDMgNzMuNTMxNiA5MC4zMTE1IDczLjc4MjJDODkuNzk1OSA3NC4wMzI5IDg5LjM1MTkgNzQuNDAxNyA4OC45Nzk1IDc0Ljg4ODdDODguNjE0MyA3NS4zNzU3IDg4LjMzMTQgNzUuOTc3MiA4OC4xMzA5IDc2LjY5MzRDODcuOTMwMyA3Ny40MDk1IDg3LjgzMDEgNzguMjMzMSA4Ny44MzAxIDc5LjE2NDFWODAuMTczOEM4Ny44MzAxIDgxLjExMiA4Ny45MzAzIDgxLjk0MjcgODguMTMwOSA4Mi42NjZDODguMzMxNCA4My4zODIyIDg4LjYxNzggODMuOTg3MyA4OC45OTAyIDg0LjQ4MTRDODkuMzY5OCA4NC45Njg0IDg5LjgxNzQgODUuMzM3MiA5MC4zMzMgODUuNTg3OUM5MC44NTU4IDg1LjgzODUgOTEuNDM5NSA4NS45NjM5IDkyLjA4NCA4NS45NjM5QzkyLjc1NzIgODUuOTYzOSA5My4zNTUxIDg1LjgzODUgOTMuODc3OSA4NS41ODc5Qzk0LjQwMDcgODUuMzM3MiA5NC44NDExIDg0Ljk2ODQgOTUuMTk5MiA4NC40ODE0Qzk1LjU2NDUgODMuOTg3MyA5NS44NDAyIDgzLjM4MjIgOTYuMDI2NCA4Mi42NjZDOTYuMjEyNiA4MS45NDI3IDk2LjMwNTcgODEuMTEyIDk2LjMwNTcgODAuMTczOFpNMTEzLjQ5MyA3MS44NTk0Vjg3LjVIMTExLjQwOUwxMDMuNTM1IDc1LjQzNjVWODcuNUgxMDEuNDYyVjcxLjg1OTRIMTAzLjUzNUwxMTEuNDQxIDgzLjk1NTFWNzEuODU5NEgxMTMuNDkzWiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfaV80NTg4Xzg4NDIwIiB4PSI0OC41IiB5PSIzNC41IiB3aWR0aD0iOTgiIGhlaWdodD0iOTYiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeD0iLTUiIGR5PSI1Ii8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjQiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0iYXJpdGhtZXRpYyIgazI9Ii0xIiBrMz0iMSIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4xNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJzaGFwZSIgcmVzdWx0PSJlZmZlY3QxX2lubmVyU2hhZG93XzQ1ODhfODg0MjAiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfZF80NTg4Xzg4NDIwIiB4PSIzNS41IiB5PSIyMC41IiB3aWR0aD0iMTI5IiBoZWlnaHQ9IjEyNyIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIgcmVzdWx0PSJoYXJkQWxwaGEiLz4KPGZlT2Zmc2V0IGR5PSI0Ii8+CjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjQiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0ib3V0Ii8+CjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA4IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3dfNDU4OF84ODQyMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd180NTg4Xzg4NDIwIiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNDU4OF84ODQyMCIgeDE9IjY1Ljg5NjQiIHkxPSIxMjQuNSIgeDI9IjEyOS41MTkiIHkyPSIzMy45MjQ4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNDQ0NDQ0MiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSJ3aGl0ZSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo=", + "description": "Sends the command to the device or updates attribute/time-series when the user pushes the button. Widget settings will enable you to configure behavior how to fetch the initial state and what to trigger when power on/off states.", + "descriptor": { + "type": "rpc", + "sizeX": 3.5, + "sizeY": 3.5, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '280px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "", + "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-power-button-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-power-button-basic-config", + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"initialState\":{\"action\":\"EXECUTE_RPC\",\"defaultValue\":false,\"executeRpc\":{\"method\":\"getState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"onUpdateState\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SHARED_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"CONSTANT\",\"constantValue\":true,\"valueToDataFunction\":\"/* Convert input boolean value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"offUpdateState\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SHARED_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"CONSTANT\",\"constantValue\":false,\"valueToDataFunction\":\"/* Convert input boolean value to RPC parameters or attribute/time-series value */ \\n return value;\"}},\"disabledState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":false,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"layout\":\"default\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorOff\":\"#A2A2A2\",\"backgroundColorOff\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Power\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":null}" + }, + "tags": [ + "command", + "downlink", + "device configuration", + "device control", + "invocation", + "remote method", + "remote function", + "interface", + "subroutine call", + "inter-process communication", + "server request", + "power" + ] +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index bb31daf7bb..c268be7bcd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -100,6 +100,9 @@ import { import { CommandButtonBasicConfigComponent } from '@home/components/widget/config/basic/button/command-button-basic-config.component'; +import { + PowerButtonBasicConfigComponent +} from '@home/components/widget/config/basic/button/power-button-basic-config.component'; @NgModule({ declarations: [ @@ -131,7 +134,8 @@ import { BarChartWithLabelsBasicConfigComponent, SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, - CommandButtonBasicConfigComponent + CommandButtonBasicConfigComponent, + PowerButtonBasicConfigComponent ], imports: [ CommonModule, @@ -167,7 +171,8 @@ import { BarChartWithLabelsBasicConfigComponent, SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, - CommandButtonBasicConfigComponent + CommandButtonBasicConfigComponent, + PowerButtonBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -197,5 +202,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type + + +
    +
    widgets.power-button.behavior
    +
    +
    widgets.rpc-state.initial-state
    + +
    +
    +
    widgets.power-button.power-on
    + +
    +
    +
    widgets.power-button.power-off
    + +
    +
    +
    widgets.rpc-state.disabled-state
    + +
    +
    +
    +
    widget-config.appearance
    + + + {{ powerButtonLayoutTranslationMap.get(layout) | translate }} + + +
    + + {{ 'widget-config.title' | translate }} + +
    + + + + + + + +
    +
    +
    + + {{ 'widget-config.card-icon' | translate }} + +
    + + + + + + + + +
    +
    +
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.power-off-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    +
    widget-config.card-appearance
    +
    +
    {{ 'widgets.background.background' | translate }}
    + + +
    +
    +
    widget-config.show-card-buttons
    + + {{ 'fullscreen.fullscreen' | translate }} + +
    +
    +
    {{ 'widget-config.card-border-radius' | translate }}
    + + + +
    +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts new file mode 100644 index 0000000000..174ded9ed3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.ts @@ -0,0 +1,195 @@ +/// +/// 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 } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { isUndefined } from '@core/utils'; +import { ValueType } from '@shared/models/constants'; +import { + powerButtonDefaultSettings, + powerButtonLayoutImages, + powerButtonLayouts, + powerButtonLayoutTranslations, + PowerButtonWidgetSettings +} from '@home/components/widget/lib/rpc/power-button-widget.models'; +import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-power-button-basic-config', + templateUrl: './power-button-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent { + + get targetDevice(): TargetDevice { + return this.powerButtonWidgetConfigForm.get('targetDevice').value; + } + + powerButtonLayouts = powerButtonLayouts; + + powerButtonLayoutTranslationMap = powerButtonLayoutTranslations; + powerButtonLayoutImageMap = powerButtonLayoutImages; + + valueType = ValueType; + + powerButtonWidgetConfigForm: UntypedFormGroup; + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.powerButtonWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: PowerButtonWidgetSettings = {...powerButtonDefaultSettings, ...(configData.config.settings || {})}; + const iconSize = resolveCssSize(configData.config.iconSize); + this.powerButtonWidgetConfigForm = this.fb.group({ + targetDevice: [configData.config.targetDevice, []], + + initialState: [settings.initialState, []], + onUpdateState: [settings.onUpdateState, []], + offUpdateState: [settings.offUpdateState, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + + showTitle: [configData.config.showTitle, []], + title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], + + showIcon: [configData.config.showTitleIcon, []], + iconSize: [iconSize[0], [Validators.min(0)]], + iconSizeUnit: [iconSize[1], []], + icon: [configData.config.titleIcon, []], + iconColor: [configData.config.iconColor, []], + + mainColorOn: [settings.mainColorOn, []], + backgroundColorOn: [settings.backgroundColorOn, []], + + mainColorOff: [settings.mainColorOff, []], + backgroundColorOff: [settings.backgroundColorOff, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + background: [settings.background, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + this.widgetConfig.config.targetDevice = config.targetDevice; + + this.widgetConfig.config.showTitle = config.showTitle; + this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; + + this.widgetConfig.config.showTitleIcon = config.showIcon; + this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); + this.widgetConfig.config.titleIcon = config.icon; + this.widgetConfig.config.iconColor = config.iconColor; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.initialState = config.initialState; + this.widgetConfig.config.settings.onUpdateState = config.onUpdateState; + this.widgetConfig.config.settings.offUpdateState = config.offUpdateState; + this.widgetConfig.config.settings.disabledState = config.disabledState; + + this.widgetConfig.config.settings.layout = config.layout; + + this.widgetConfig.config.settings.mainColorOn = config.mainColorOn; + this.widgetConfig.config.settings.backgroundColorOn = config.backgroundColorOn; + + this.widgetConfig.config.settings.mainColorOff = config.mainColorOff; + this.widgetConfig.config.settings.backgroundColorOff = config.backgroundColorOff; + + this.widgetConfig.config.settings.mainColorDisabled = config.mainColorDisabled; + this.widgetConfig.config.settings.backgroundColorDisabled = config.backgroundColorDisabled; + + this.widgetConfig.config.settings.background = config.background; + + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + + this.widgetConfig.config.actions = config.actions; + return this.widgetConfig; + } + + protected validatorTriggers(): string[] { + return ['showTitle', 'showIcon']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showTitle: boolean = this.powerButtonWidgetConfigForm.get('showTitle').value; + const showIcon: boolean = this.powerButtonWidgetConfigForm.get('showIcon').value; + if (showTitle) { + this.powerButtonWidgetConfigForm.get('title').enable(); + this.powerButtonWidgetConfigForm.get('titleFont').enable(); + this.powerButtonWidgetConfigForm.get('titleColor').enable(); + this.powerButtonWidgetConfigForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.powerButtonWidgetConfigForm.get('iconSize').enable(); + this.powerButtonWidgetConfigForm.get('iconSizeUnit').enable(); + this.powerButtonWidgetConfigForm.get('icon').enable(); + this.powerButtonWidgetConfigForm.get('iconColor').enable(); + } else { + this.powerButtonWidgetConfigForm.get('iconSize').disable(); + this.powerButtonWidgetConfigForm.get('iconSizeUnit').disable(); + this.powerButtonWidgetConfigForm.get('icon').disable(); + this.powerButtonWidgetConfigForm.get('iconColor').disable(); + } + } else { + this.powerButtonWidgetConfigForm.get('title').disable(); + this.powerButtonWidgetConfigForm.get('titleFont').disable(); + this.powerButtonWidgetConfigForm.get('titleColor').disable(); + this.powerButtonWidgetConfigForm.get('showIcon').disable({emitEvent: false}); + this.powerButtonWidgetConfigForm.get('iconSize').disable(); + this.powerButtonWidgetConfigForm.get('iconSizeUnit').disable(); + this.powerButtonWidgetConfigForm.get('icon').disable(); + this.powerButtonWidgetConfigForm.get('iconColor').disable(); + } + } + + private getCardButtons(config: WidgetConfig): string[] { + const buttons: string[] = []; + if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { + buttons.push('fullscreen'); + } + return buttons; + } + + private setCardButtons(buttons: string[], config: WidgetConfig) { + config.enableFullscreen = buttons.includes('fullscreen'); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index 26dc76866a..dc56a1e992 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -91,6 +91,8 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A ngOnDestroy() { this.valueActions.forEach(v => v.destroy()); + this.loadingSubject.complete(); + this.loadingSubject.unsubscribe(); } public onInit() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html new file mode 100644 index 0000000000..93e394ac5e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html @@ -0,0 +1,32 @@ + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss new file mode 100644 index 0000000000..e0192630f9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss @@ -0,0 +1,64 @@ +/** + * 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. + */ +.tb-power-button-panel { + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 24px 24px 24px; + > div:not(.tb-power-button-overlay) { + z-index: 1; + } + .tb-power-button-overlay { + position: absolute; + top: 12px; + left: 12px; + bottom: 12px; + right: 12px; + } + div.tb-widget-title { + padding: 0; + } + .tb-power-button-content { + flex: 1; + min-width: 0; + min-height: 0; + .tb-power-button-shape { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + svg { + .tb-small-shadow { + filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.08)); + } + .tb-shadow { + filter: drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.1)); + } + } + &.tb-power-button-pointer { + svg { + .tb-hover-circle { + cursor: pointer; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts new file mode 100644 index 0000000000..e68a2e4baa --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts @@ -0,0 +1,200 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; +import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; +import { Observable } from 'rxjs'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ValueType } from '@shared/models/constants'; +import { + powerButtonDefaultSettings, + PowerButtonShape, + powerButtonShapeSize, + PowerButtonWidgetSettings +} from '@home/components/widget/lib/rpc/power-button-widget.models'; +import { SVG, Svg } from '@svgdotjs/svg.js'; + +@Component({ + selector: 'tb-power-button-widget', + templateUrl: './power-button-widget.component.html', + styleUrls: ['../action/action-widget.scss', './power-button-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class PowerButtonWidgetComponent extends + BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('powerButtonShape', {static: false}) + powerButtonShape: ElementRef; + + settings: PowerButtonWidgetSettings; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + + value = false; + disabled = false; + + private shapeResize$: ResizeObserver; + private drawSvgShapePending = false; + private svgShape: Svg; + private powerButtonSvgShape: PowerButtonShape; + private disabledState = false; + + private onValueSetter: ValueSetter; + private offValueSetter: ValueSetter; + + constructor(protected imagePipe: ImagePipe, + protected sanitizer: DomSanitizer, + private renderer: Renderer2, + protected cd: ChangeDetectorRef) { + super(cd); + } + + ngOnInit(): void { + super.ngOnInit(); + this.settings = {...powerButtonDefaultSettings, ...this.ctx.settings}; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + + const getInitialStateSettings = + {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')}; + this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onValue(value) + }); + + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onDisabled(value) + }); + + const onUpdateStateSettings = {...this.settings.onUpdateState, + actionLabel: this.ctx.translate.instant('widgets.power-button.power-on')}; + this.onValueSetter = this.createValueSetter(onUpdateStateSettings); + + const offUpdateStateSettings = {...this.settings.offUpdateState, + actionLabel: this.ctx.translate.instant('widgets.power-button.power-off')}; + this.offValueSetter = this.createValueSetter(offUpdateStateSettings); + + this.loading$.subscribe((loading) => { + this.updateDisabledState(loading || this.disabled); + }); + } + + ngAfterViewInit(): void { + if (this.drawSvgShapePending) { + this.drawSvg(); + } + super.ngAfterViewInit(); + } + + ngOnDestroy() { + if (this.shapeResize$) { + this.shapeResize$.disconnect(); + } + super.ngOnDestroy(); + } + + public onInit() { + super.onInit(); + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + if (this.powerButtonShape) { + this.drawSvg(); + } else { + this.drawSvgShapePending = true; + } + this.cd.detectChanges(); + } + + private onValue(value: boolean): void { + const newValue = !!value; + if (this.value !== newValue) { + this.value = newValue; + this.powerButtonSvgShape?.setValue(this.value); + } + } + + private onDisabled(value: boolean): void { + const newDisabled = !!value; + if (this.disabled !== newDisabled) { + this.disabled = newDisabled; + this.updateDisabledState(this.disabled); + } + } + + private onClick() { + if (!this.ctx.isEdit && !this.ctx.isPreview && !this.disabledState) { + this.onValue(!this.value); + const targetValue = this.value; + const targetSetter = targetValue ? this.onValueSetter : this.offValueSetter; + this.powerButtonSvgShape?.setPressed(true); + this.updateValue(targetSetter, targetValue, { + next: () => { + this.powerButtonSvgShape?.setPressed(false); + this.onValue(targetValue); + }, + error: () => { + this.powerButtonSvgShape?.setPressed(false); + this.onValue(!targetValue); + } + }); + } + } + + private drawSvg() { + this.svgShape = SVG().addTo(this.powerButtonShape.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize); + this.renderer.setStyle(this.svgShape.node, 'overflow', 'visible'); + this.renderer.setStyle(this.svgShape.node, 'user-select', 'none'); + + this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, + this.settings, this.value, this.disabledState, () => this.onClick()); + + this.shapeResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.shapeResize$.observe(this.powerButtonShape.nativeElement); + this.onResize(); + } + + private updateDisabledState(disabled: boolean) { + this.disabledState = disabled; + this.powerButtonSvgShape?.setDisabled(this.disabledState); + } + + private onResize() { + const shapeWidth = this.powerButtonShape.nativeElement.getBoundingClientRect().width; + const shapeHeight = this.powerButtonShape.nativeElement.getBoundingClientRect().height; + const size = Math.min(shapeWidth, shapeHeight); + const scale = size / powerButtonShapeSize; + this.renderer.setStyle(this.svgShape.node, 'transform', `scale(${scale})`); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts new file mode 100644 index 0000000000..b1372a9660 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts @@ -0,0 +1,912 @@ +/// +/// 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 { BackgroundSettings, BackgroundType } from '@shared/models/widget-settings.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { + DataToValueType, + GetValueAction, + GetValueSettings, + SetValueAction, + SetValueSettings, + ValueToDataType +} from '@shared/models/action-widget-settings.models'; +import { Circle, Element, G, Gradient, Runner, Stop, Svg, Text, Timeline } from '@svgdotjs/svg.js'; +import tinycolor from 'tinycolor2'; +import { WidgetContext } from '@home/models/widget-component.models'; + +export enum PowerButtonLayout { + default = 'default', + simplified = 'simplified', + outlined = 'outlined', + default_volume = 'default_volume', + simplified_volume = 'simplified_volume', + outlined_volume = 'outlined_volume' +} + +export const powerButtonLayouts = Object.keys(PowerButtonLayout) as PowerButtonLayout[]; + +export const powerButtonLayoutTranslations = new Map( + [ + [PowerButtonLayout.default, 'widgets.power-button.layout-default'], + [PowerButtonLayout.simplified, 'widgets.power-button.layout-simplified'], + [PowerButtonLayout.outlined, 'widgets.power-button.layout-outlined'], + [PowerButtonLayout.default_volume, 'widgets.power-button.layout-default-volume'], + [PowerButtonLayout.simplified_volume, 'widgets.power-button.layout-simplified-volume'], + [PowerButtonLayout.outlined_volume, 'widgets.power-button.layout-outlined-volume'] + ] +); + +export const powerButtonLayoutImages = new Map( + [ + [PowerButtonLayout.default, 'assets/widget/power-button/default-layout.svg'], + [PowerButtonLayout.simplified, 'assets/widget/power-button/simplified-layout.svg'], + [PowerButtonLayout.outlined, 'assets/widget/power-button/outlined-layout.svg'], + [PowerButtonLayout.default_volume, 'assets/widget/power-button/default-volume-layout.svg'], + [PowerButtonLayout.simplified_volume, 'assets/widget/power-button/simplified-volume-layout.svg'], + [PowerButtonLayout.outlined_volume, 'assets/widget/power-button/outlined-volume-layout.svg'] + ] +); + +export interface PowerButtonWidgetSettings { + initialState: GetValueSettings; + disabledState: GetValueSettings; + onUpdateState: SetValueSettings; + offUpdateState: SetValueSettings; + layout: PowerButtonLayout; + mainColorOn: string; + backgroundColorOn: string; + mainColorOff: string; + backgroundColorOff: string; + mainColorDisabled: string; + backgroundColorDisabled: string; + background: BackgroundSettings; +} + +export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { + initialState: { + action: GetValueAction.EXECUTE_RPC, + defaultValue: false, + executeRpc: { + method: 'getState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + onUpdateState: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SHARED_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.CONSTANT, + constantValue: true, + valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + offUpdateState: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SHARED_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.CONSTANT, + constantValue: false, + valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;' + } + }, + layout: PowerButtonLayout.default, + mainColorOn: '#3F52DD', + backgroundColorOn: '#FFFFFF', + mainColorOff: '#A2A2A2', + backgroundColorOff: '#FFFFFF', + mainColorDisabled: 'rgba(0,0,0,0.12)', + backgroundColorDisabled: '#FFFFFF', + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + } +}; + +interface PowerButtonColor { + hex: string; + opacity: number; +} + +type PowerButtonState = 'on' | 'off' | 'disabled'; + +interface PowerButtonColorState { + mainColor: PowerButtonColor; + backgroundColor: PowerButtonColor; +} + +type PowerButtonShapeColors = Record; + +const createPowerButtonShapeColors = (settings: PowerButtonWidgetSettings): PowerButtonShapeColors => { + const mainColorOn = tinycolor(settings.mainColorOn); + const backgroundColorOn = tinycolor(settings.backgroundColorOn); + const mainColorOff = tinycolor(settings.mainColorOff); + const backgroundColorOff = tinycolor(settings.backgroundColorOff); + const mainColorDisabled = tinycolor(settings.mainColorDisabled); + const backgroundColorDisabled = tinycolor(settings.backgroundColorDisabled); + return { + on: { + mainColor: {hex: mainColorOn.toHexString(), opacity: mainColorOn.getAlpha()}, + backgroundColor: {hex: backgroundColorOn.toHexString(), opacity: backgroundColorOn.getAlpha()}, + }, + off: { + mainColor: {hex: mainColorOff.toHexString(), opacity: mainColorOff.getAlpha()}, + backgroundColor: {hex: backgroundColorOff.toHexString(), opacity: backgroundColorOff.getAlpha()}, + }, + disabled: { + mainColor: {hex: mainColorDisabled.toHexString(), opacity: mainColorDisabled.getAlpha()}, + backgroundColor: {hex: backgroundColorDisabled.toHexString(), opacity: backgroundColorDisabled.getAlpha()}, + } + }; +}; + +export const powerButtonShapeSize = 110; +const cx = powerButtonShapeSize / 2; +const cy = powerButtonShapeSize / 2; + +export abstract class PowerButtonShape { + + static fromSettings(ctx: WidgetContext, + svgShape: Svg, + settings: PowerButtonWidgetSettings, + value: boolean, + disabled: boolean, + onClick: () => void): PowerButtonShape { + switch (settings.layout) { + case PowerButtonLayout.default: + return new DefaultPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + case PowerButtonLayout.simplified: + return new SimplifiedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + case PowerButtonLayout.outlined: + return new OutlinedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + case PowerButtonLayout.default_volume: + return new DefaultVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + case PowerButtonLayout.simplified_volume: + return new SimplifiedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + case PowerButtonLayout.outlined_volume: + return new OutlinedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); + } + } + + protected readonly colors: PowerButtonShapeColors; + protected readonly onLabel: string; + protected readonly offLabel: string; + + protected backgroundShape: Circle; + protected hoverShape: Circle; + protected hovered = false; + protected pressed = false; + protected forcePressed = false; + + protected constructor(protected widgetContext: WidgetContext, + protected svgShape: Svg, + protected settings: PowerButtonWidgetSettings, + protected value: boolean, + protected disabled: boolean, + protected onClick: () => void) { + this.colors = createPowerButtonShapeColors(this.settings); + this.onLabel = this.widgetContext.translate.instant('widgets.power-button.on-label').toUpperCase(); + this.offLabel = this.widgetContext.translate.instant('widgets.power-button.off-label').toUpperCase(); + this._drawShape(); + } + + public setValue(value: boolean) { + if (this.value !== value) { + this.value = value; + this._drawState(); + } + } + + public setDisabled(disabled: boolean) { + if (this.disabled !== disabled) { + this.disabled = disabled; + this._drawState(); + } + } + + public setPressed(pressed: boolean) { + if (this.forcePressed !== pressed) { + this.forcePressed = pressed; + if (this.forcePressed && !this.pressed) { + this.onPressStart(); + } else if (!this.forcePressed && !this.pressed) { + this.onPressEnd(); + } + } + } + + private _drawShape() { + + this.backgroundShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + + this.drawShape(); + + this.hoverShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy).addClass('tb-hover-circle') + .fill({color: '#000000', opacity: 0}); + this.hoverShape.on('mouseover', () => { + this.hovered = true; + if (!this.disabled) { + this.hoverShape.timeline().finish(); + this.hoverShape.animate(200).attr({'fill-opacity': 0.06}); + } + }); + this.hoverShape.on('mouseout', () => { + this.hovered = false; + this.hoverShape.timeline().finish(); + this.hoverShape.animate(200).attr({'fill-opacity': 0}); + this._cancelPressed(); + }); + this.hoverShape.on('touchmove', (event: TouchEvent) => { + const touch = event.touches[0]; + const element = document.elementFromPoint(touch.pageX,touch.pageY); + if (this.hoverShape.node !== element) { + this._cancelPressed(); + } + }); + this.hoverShape.on('touchcancel', () => { + this._cancelPressed(); + }); + this.hoverShape.on('mousedown touchstart', (event: Event) => { + if (event.type === 'mousedown') { + if ((event as MouseEvent).button !== 0) { + return; + } + } + if (!this.disabled && !this.pressed) { + this.pressed = true; + if (!this.forcePressed) { + this.onPressStart(); + } + } + }); + this.hoverShape.on('mouseup touchend touchcancel', () => { + if (this.pressed && !this.disabled) { + this.onClick(); + } + this._cancelPressed(); + }); + this._drawState(); + } + + private _cancelPressed() { + if (this.pressed) { + this.pressed = false; + if (!this.forcePressed) { + this.onPressEnd(); + } + } + } + + private _drawState() { + let colorState: PowerButtonColorState; + if (this.disabled) { + colorState = this.colors.disabled; + } else { + colorState = this.value ? this.colors.on : this.colors.off; + } + this.drawBackgroundState(colorState.backgroundColor); + this.drawColorState(colorState.mainColor); + if (this.value) { + this.drawOn(); + } else { + this.drawOff(); + } + if (this.disabled) { + this.hoverShape.timeline().finish(); + this.hoverShape.attr({'fill-opacity': 0}); + } else if (this.hovered) { + this.hoverShape.timeline().finish(); + this.hoverShape.animate(200).attr({'fill-opacity': 0.06}); + } + } + + private drawBackgroundState(backgroundColor: PowerButtonColor) { + this.backgroundShape.attr({ fill: backgroundColor.hex, 'fill-opacity': backgroundColor.opacity}); + } + + protected drawShape() {} + + protected drawColorState(_mainColor: PowerButtonColor) {} + + protected drawOff() {} + + protected drawOn() {} + + protected onPressStart() {} + + protected onPressEnd() {} + + protected createMask(shape: Element, maskElements: Element[]) { + const mask = + this.svgShape.mask().add(this.svgShape.rect().width('100%').height('100%').fill('#fff')); + maskElements.forEach(e => { + mask.add(e.fill('#000').attr({'fill-opacity': 1})); + }); + shape.maskWith(mask); + } + + protected createPressedShadow(diameter: number, rightElseLeft = true): Gradient { + const innerShadowGradient = this.svgShape.gradient('radial', (add) => { + add.stop(0.7, '#000000', 0); + add.stop(1, '#000000', 0.4); + }).attr({ cx: rightElseLeft ? '45%' : '55%', cy: '55%', r: '100%'}); + this.svgShape.circle(diameter).center(cx, cy) + .fill(innerShadowGradient).stroke({width: 0}); + return innerShadowGradient; + } + + protected pressedAnimation(element: Element): Runner { + return element.animate(200, 0, 'now'); + } + + protected createOnLabel(fontWeight = '500'): Text { + return this.createLabel(this.onLabel, fontWeight); + } + + protected createOffLabel(fontWeight = '500'): Text { + return this.createLabel(this.offLabel, fontWeight); + } + + private createLabel(text: string, fontWeight = '500'): Text { + return this.svgShape.text(text).font({ + family: 'Roboto', + weight: fontWeight, + style: 'normal', + size: '22px' + }).attr({x: '50%', y: '50%', 'text-anchor': 'middle', 'dominant-baseline': 'middle'}); + } + +} + +class DefaultPowerButtonShape extends PowerButtonShape { + + private outerBorder: Circle; + private outerBorderMask: Circle; + private offLabelShape: Text; + private onCircleShape: Circle; + private onLabelShape: Text; + private pressedShadow: Gradient; + private pressedTimeline: Timeline; + private centerGroup: G; + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20) + .center(cx, cy); + this.onLabelShape = this.createOnLabel(); + this.createMask(this.onCircleShape, [this.onLabelShape]); + this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 20); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onLabelShape.timeline(this.pressedTimeline); + this.pressedShadow.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor) { + this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + this.outerBorderMask.radius((powerButtonShapeSize - 20)/2); + this.onCircleShape.hide(); + this.centerGroup.show(); + } + + protected drawOn() { + this.outerBorderMask.radius((powerButtonShapeSize - 2)/2); + this.centerGroup.hide(); + this.onCircleShape.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + const pressedScale = 0.75; + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + } + +} + +class SimplifiedPowerButtonShape extends PowerButtonShape { + + private outerBorder: Circle; + private outerBorderMask: Circle; + private onCircleShape: Circle; + private offLabelShape: Text; + private onLabelShape: Text; + private pressedShadow: Gradient; + private pressedTimeline: Timeline; + private centerGroup: G; + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy); + this.onLabelShape = this.createOnLabel(); + this.createMask(this.onCircleShape, [this.onLabelShape]); + this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 4); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onLabelShape.timeline(this.pressedTimeline); + this.pressedShadow.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor) { + this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + this.onCircleShape.hide(); + this.outerBorder.show(); + this.centerGroup.show(); + } + + protected drawOn() { + this.centerGroup.hide(); + this.outerBorder.hide(); + this.onCircleShape.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + const pressedScale = 0.75; + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + } +} + +class OutlinedPowerButtonShape extends PowerButtonShape { + private outerBorder: Circle; + private outerBorderMask: Circle; + private innerBorder: Circle; + private innerBorderMask: Circle; + private offLabelShape: Text; + private onCircleShape: Circle; + private onLabelShape: Text; + private pressedShadow: Gradient; + private pressedTimeline: Timeline; + private centerGroup: G; + private onCenterGroup: G; + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 2).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); + this.createMask(this.innerBorder, [this.innerBorderMask]); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.onCenterGroup = this.svgShape.group(); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy) + .addTo(this.onCenterGroup); + this.onLabelShape = this.createOnLabel(); + this.createMask(this.onCircleShape, [this.onLabelShape]); + this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 24); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onCenterGroup.timeline(this.pressedTimeline); + this.onLabelShape.timeline(this.pressedTimeline); + this.pressedShadow.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor) { + this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.innerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + this.onCenterGroup.hide(); + this.centerGroup.show(); + } + + protected drawOn() { + this.centerGroup.hide(); + this.onCenterGroup.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + const pressedScale = 0.75; + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onCenterGroup).transform({scale: 0.98}); + this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); + this.pressedAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + } +} + +class DefaultVolumePowerButtonShape extends PowerButtonShape { + private outerBorder: Circle; + private outerBorderMask: Circle; + private outerBorderGradient: Gradient; + private innerBorder: Circle; + private innerBorderMask: Circle; + private innerBorderGradient: Gradient; + private innerShadow: Circle; + private innerShadowGradient: Gradient; + private innerShadowGradientStop: Stop; + private offLabelShape: Text; + private onCircleShape: Circle; + private onLabelShape: Text; + private pressedTimeline: Timeline; + private centerGroup: G; + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.outerBorderGradient = this.svgShape.gradient('linear', (add) => { + add.stop(0, '#CCCCCC', 1); + add.stop(1, '#FFFFFF', 1); + }).from(0.268, 0.92).to(0.832, 0.1188); + this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); + this.createMask(this.innerBorder, [this.innerBorderMask]); + this.innerBorderGradient = this.svgShape.gradient('linear', (add) => { + add.stop(0, '#CCCCCC', 1); + add.stop(1, '#FFFFFF', 1); + }).from(0.832, 0.1188).to(0.268, 0.92); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel('400').addTo(this.centerGroup); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); + this.onLabelShape = this.createOnLabel('400'); + this.createMask(this.onCircleShape, [this.onLabelShape]); + this.innerShadow = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); + this.innerShadowGradient = this.svgShape.gradient('radial', (add) => { + add.stop(0.7, '#000000', 0); + this.innerShadowGradientStop = add.stop(1, '#000000', 0.2); + }).attr({ cx: '45%', cy: '55%', r: '65%'}); + this.innerShadow.fill(this.innerShadowGradient); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onLabelShape.timeline(this.pressedTimeline); + this.innerShadowGradient.timeline(this.pressedTimeline); + this.innerShadowGradientStop.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor){ + if (this.disabled) { + this.backgroundShape.removeClass('tb-small-shadow'); + if (!this.forcePressed) { + this.innerShadow.hide(); + } + this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.innerBorder.attr({fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } else { + this.backgroundShape.addClass('tb-small-shadow'); + this.innerShadow.show(); + this.outerBorder.fill(this.outerBorderGradient); + this.outerBorder.attr({ 'fill-opacity': 1 }); + this.innerBorder.fill(this.innerBorderGradient); + this.innerBorder.attr({ 'fill-opacity': 1 }); + } + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + this.onCircleShape.hide(); + this.centerGroup.show(); + this.innerBorder.show(); + } + + protected drawOn() { + if (this.disabled) { + this.innerBorder.hide(); + } else { + this.innerBorder.show(); + } + this.centerGroup.hide(); + this.onCircleShape.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + this.innerShadow.show(); + const pressedScale = 0.75; + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.pressedAnimation(this.innerShadowGradient).attr({ r: '60%'}); + this.pressedAnimation(this.innerShadowGradientStop).update({ opacity: 0.4 }); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedAnimation(this.innerShadowGradient).attr({ r: '65%'}).after(() => { + if (this.disabled) { + this.innerShadow.hide(); + } + }); + this.pressedAnimation(this.innerShadowGradientStop).update({ opacity: 0.2 }); + } + +} + +class SimplifiedVolumePowerButtonShape extends PowerButtonShape { + + private outerBorder: Circle; + private outerBorderMask: Circle; + private offLabelShape: Text; + private onLabelShape: Text; + private innerShadow: Circle; + private pressedShadow: Gradient; + private pressedTimeline: Timeline; + private centerGroup: G; + private onCenterGroup: G; + + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({color: '#FAFAFA', opacity: 1}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); + this.onCenterGroup = this.svgShape.group(); + this.onLabelShape = this.createOnLabel().addTo(this.onCenterGroup); + this.innerShadow = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); + const innerShadowGradient = this.svgShape.gradient('radial', (add) => { + add.stop(0.7, '#000000', 0); + add.stop(1, '#000000', 0.2); + }).attr({ cx: '55%', cy: '55%', r: '65%'}); + this.innerShadow.fill(innerShadowGradient); + this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 4, false); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onCenterGroup.timeline(this.pressedTimeline); + this.pressedShadow.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor){ + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + if (!this.pressed) { + this.backgroundShape.addClass('tb-shadow'); + } + this.innerShadow.hide(); + this.onCenterGroup.hide(); + this.centerGroup.show(); + } + + protected drawOn() { + this.backgroundShape.removeClass('tb-shadow'); + this.centerGroup.hide(); + this.onCenterGroup.show(); + this.innerShadow.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + const pressedScale = 0.75; + if (!this.value) { + this.backgroundShape.removeClass('tb-shadow'); + } + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onCenterGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); + this.pressedAnimation(this.pressedShadow).attr({r: '100%'}).after(() => { + if (!this.value) { + this.backgroundShape.addClass('tb-shadow'); + } + }); + } +} + +class OutlinedVolumePowerButtonShape extends PowerButtonShape { + private outerBorder: Circle; + private outerBorderMask: Circle; + private outerBorderGradient: Gradient; + private innerBorder: Circle; + private innerBorderMask: Circle; + private offLabelShape: Text; + private onCircleShape: Circle; + private onLabelShape: Text; + private pressedShadow: Gradient; + private pressedTimeline: Timeline; + private centerGroup: G; + private onCenterGroup: G; + + protected drawShape() { + this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); + this.createMask(this.outerBorder, [this.outerBorderMask]); + this.outerBorderGradient = this.svgShape.gradient('linear', (add) => { + add.stop(0, '#CCCCCC', 1); + add.stop(1, '#FFFFFF', 1); + }).from(0.268, 0.92).to(0.832, 0.1188); + this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) + .fill({opacity: 0}).stroke({width: 0}); + this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy); + this.createMask(this.innerBorder, [this.innerBorderMask]); + this.centerGroup = this.svgShape.group(); + this.offLabelShape = this.createOffLabel('800').addTo(this.centerGroup); + this.onCenterGroup = this.svgShape.group(); + this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy) + .addTo(this.onCenterGroup); + this.onLabelShape = this.createOnLabel('800'); + this.createMask(this.onCircleShape, [this.onLabelShape]); + this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 30); + this.backgroundShape.addClass('tb-small-shadow'); + + this.pressedTimeline = new Timeline(); + this.centerGroup.timeline(this.pressedTimeline); + this.onCenterGroup.timeline(this.pressedTimeline); + this.onLabelShape.timeline(this.pressedTimeline); + this.pressedShadow.timeline(this.pressedTimeline); + } + + protected drawColorState(mainColor: PowerButtonColor){ + if (this.disabled) { + this.outerBorder.attr({ fill: '#000000', 'fill-opacity': 0.03}); + } else { + this.outerBorder.fill(this.outerBorderGradient); + this.outerBorder.attr({ 'fill-opacity': 1 }); + } + this.innerBorder.attr({fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); + } + + protected drawOff() { + this.onCenterGroup.hide(); + this.centerGroup.show(); + this.innerBorder.show(); + } + + protected drawOn() { + this.innerBorder.hide(); + this.centerGroup.hide(); + this.onCenterGroup.show(); + } + + protected onPressStart() { + this.pressedTimeline.finish(); + const pressedScale = 0.75; + this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); + this.pressedAnimation(this.onCenterGroup).transform({scale: 0.98}); + this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + } + + protected onPressEnd() { + this.pressedTimeline.finish(); + this.pressedAnimation(this.centerGroup).transform({scale: 1}); + this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); + this.pressedAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html new file mode 100644 index 0000000000..ced9bf9575 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html @@ -0,0 +1,142 @@ + + +
    +
    widgets.power-button.behavior
    +
    +
    widgets.rpc-state.initial-state
    + +
    +
    +
    widgets.power-button.power-on
    + +
    +
    +
    widgets.power-button.power-off
    + +
    +
    +
    widgets.rpc-state.disabled-state
    + +
    +
    +
    +
    widget-config.card-style
    + + + {{ powerButtonLayoutTranslationMap.get(layout) | translate }} + + +
    +
    {{ 'widgets.background.background' | translate }}
    + + +
    +
    +
    +
    widgets.power-button.button
    +
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.power-off-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    +
    +
    +
    widgets.power-button.main
    + + +
    + +
    +
    widgets.power-button.background
    + + +
    +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.ts new file mode 100644 index 0000000000..c1d9f98460 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.ts @@ -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 { Component } from '@angular/core'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ValueType } from '@shared/models/constants'; +import { + powerButtonDefaultSettings, + powerButtonLayoutImages, + powerButtonLayouts, + powerButtonLayoutTranslations +} from '@home/components/widget/lib/rpc/power-button-widget.models'; + +@Component({ + selector: 'tb-power-button-widget-settings', + templateUrl: './power-button-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class PowerButtonWidgetSettingsComponent extends WidgetSettingsComponent { + + get targetDevice(): TargetDevice { + return this.widgetConfig?.config?.targetDevice; + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + + powerButtonLayouts = powerButtonLayouts; + + powerButtonLayoutTranslationMap = powerButtonLayoutTranslations; + powerButtonLayoutImageMap = powerButtonLayoutImages; + + valueType = ValueType; + + powerButtonWidgetSettingsForm: UntypedFormGroup; + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.powerButtonWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...powerButtonDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.powerButtonWidgetSettingsForm = this.fb.group({ + initialState: [settings.initialState, []], + onUpdateState: [settings.onUpdateState, []], + offUpdateState: [settings.offUpdateState, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + + mainColorOn: [settings.mainColorOn, []], + backgroundColorOn: [settings.backgroundColorOn, []], + + mainColorOff: [settings.mainColorOff, []], + backgroundColorOff: [settings.backgroundColorOff, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + background: [settings.background, []] + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 3f4a619c49..d3fe9dac3e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -321,6 +321,9 @@ import { import { CommandButtonWidgetSettingsComponent } from '@home/components/widget/lib/settings/button/command-button-widget-settings.component'; +import { + PowerButtonWidgetSettingsComponent +} from '@home/components/widget/lib/settings/button/power-button-widget-settings.component'; @NgModule({ declarations: [ @@ -436,7 +439,8 @@ import { BarChartWithLabelsWidgetSettingsComponent, SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, - CommandButtonWidgetSettingsComponent + CommandButtonWidgetSettingsComponent, + PowerButtonWidgetSettingsComponent ], imports: [ CommonModule, @@ -557,7 +561,8 @@ import { BarChartWithLabelsWidgetSettingsComponent, SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, - CommandButtonWidgetSettingsComponent + CommandButtonWidgetSettingsComponent, + PowerButtonWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -645,5 +650,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/power-button/default-volume-layout.svg b/ui-ngx/src/assets/widget/power-button/default-volume-layout.svg new file mode 100644 index 0000000000..9a502cc8f0 --- /dev/null +++ b/ui-ngx/src/assets/widget/power-button/default-volume-layout.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/power-button/outlined-layout.svg b/ui-ngx/src/assets/widget/power-button/outlined-layout.svg new file mode 100644 index 0000000000..9510d68721 --- /dev/null +++ b/ui-ngx/src/assets/widget/power-button/outlined-layout.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/power-button/outlined-volume-layout.svg b/ui-ngx/src/assets/widget/power-button/outlined-volume-layout.svg new file mode 100644 index 0000000000..9e511892b3 --- /dev/null +++ b/ui-ngx/src/assets/widget/power-button/outlined-volume-layout.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/power-button/simplified-layout.svg b/ui-ngx/src/assets/widget/power-button/simplified-layout.svg new file mode 100644 index 0000000000..eecb1e9cbe --- /dev/null +++ b/ui-ngx/src/assets/widget/power-button/simplified-layout.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/power-button/simplified-volume-layout.svg b/ui-ngx/src/assets/widget/power-button/simplified-volume-layout.svg new file mode 100644 index 0000000000..500d9a0c97 --- /dev/null +++ b/ui-ngx/src/assets/widget/power-button/simplified-volume-layout.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 753f502891b8d1cfddd7d14c639b09267639bf5f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Feb 2024 19:57:47 +0200 Subject: [PATCH 114/137] UI: Update power button widget default settings. --- .../src/main/data/json/system/widget_types/power_button.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_types/power_button.json b/application/src/main/data/json/system/widget_types/power_button.json index 2f825c9e74..d90439d82f 100644 --- a/application/src/main/data/json/system/widget_types/power_button.json +++ b/application/src/main/data/json/system/widget_types/power_button.json @@ -17,7 +17,7 @@ "settingsDirective": "tb-power-button-widget-settings", "hasBasicMode": true, "basicModeDirective": "tb-power-button-basic-config", - "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"initialState\":{\"action\":\"EXECUTE_RPC\",\"defaultValue\":false,\"executeRpc\":{\"method\":\"getState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"onUpdateState\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SHARED_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"CONSTANT\",\"constantValue\":true,\"valueToDataFunction\":\"/* Convert input boolean value to RPC parameters or attribute/time-series value */\\nreturn value;\"}},\"offUpdateState\":{\"action\":\"EXECUTE_RPC\",\"executeRpc\":{\"method\":\"setState\",\"requestTimeout\":5000,\"requestPersistent\":false,\"persistentPollingInterval\":1000},\"setAttribute\":{\"key\":\"state\",\"scope\":\"SHARED_SCOPE\"},\"putTimeSeries\":{\"key\":\"state\"},\"valueToData\":{\"type\":\"CONSTANT\",\"constantValue\":false,\"valueToDataFunction\":\"/* Convert input boolean value to RPC parameters or attribute/time-series value */ \\n return value;\"}},\"disabledState\":{\"action\":\"DO_NOTHING\",\"defaultValue\":false,\"getAttribute\":{\"key\":\"state\",\"scope\":null},\"getTimeSeries\":{\"key\":\"state\"},\"dataToValue\":{\"type\":\"NONE\",\"compareToValue\":true,\"dataToValueFunction\":\"/* Should return boolean value */\\nreturn data;\"}},\"layout\":\"default\",\"mainColorOn\":\"#3F52DD\",\"backgroundColorOn\":\"#FFFFFF\",\"mainColorOff\":\"#A2A2A2\",\"backgroundColorOff\":\"#FFFFFF\",\"mainColorDisabled\":\"rgba(0,0,0,0.12)\",\"backgroundColorDisabled\":\"#FFFFFF\",\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Power\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":null}" + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Power\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":null,\"lineHeight\":\"1.6\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"mdi:lightbulb-outline\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"configMode\":\"basic\",\"targetDevice\":null,\"titleColor\":null,\"borderRadius\":null}" }, "tags": [ "command", From e0e3feb0d8708c3481c9b8c2479dd8bdb9cac462 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 9 Feb 2024 09:52:25 +0200 Subject: [PATCH 115/137] Fix tests --- .../util/EntitiesFieldsAsyncLoaderTest.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java index 5967023354..f3e61dcffc 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoaderTest.java @@ -37,10 +37,12 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; @@ -52,6 +54,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -95,6 +98,8 @@ public class EntitiesFieldsAsyncLoaderTest { private RuleChainService ruleChainServiceMock; @Mock private EntityViewService entityViewServiceMock; + @Mock + private EdgeService edgeServiceMock; @BeforeAll public static void setup() { @@ -108,7 +113,8 @@ public class EntitiesFieldsAsyncLoaderTest { EntityType.DEVICE, EntityType.ALARM, EntityType.RULE_CHAIN, - EntityType.ENTITY_VIEW + EntityType.ENTITY_VIEW, + EntityType.EDGE ); } @@ -228,6 +234,14 @@ public class EntitiesFieldsAsyncLoaderTest { when(ctxMock.getEntityViewService()).thenReturn(entityViewServiceMock); doReturn(entityView).when(entityViewServiceMock).findEntityViewByIdAsync(eq(TENANT_ID), any()); + break; + case EDGE: + var edge = Futures.immediateFuture(entityDoesNotExist ? null : new Edge(new EdgeId(RANDOM_UUID))); + + when(ctxMock.getDbCallbackExecutor()).thenReturn(DB_EXECUTOR); + when(ctxMock.getEdgeService()).thenReturn(edgeServiceMock); + doReturn(edge).when(edgeServiceMock).findEdgeByIdAsync(eq(TENANT_ID), any()); + break; default: throw new RuntimeException("Unexpected EntityType: " + entityType); @@ -252,6 +266,8 @@ public class EntitiesFieldsAsyncLoaderTest { return new RuleChain((RuleChainId) entityId); case ENTITY_VIEW: return new EntityView((EntityViewId) entityId); + case EDGE: + return new Edge((EdgeId) entityId); default: throw new RuntimeException("Unexpected EntityType: " + entityId.getEntityType()); } From c62a95835374ddc1dd2cd73ae4ae4380d2b2468c Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 9 Feb 2024 10:46:14 +0200 Subject: [PATCH 116/137] Improve DeviceEdgeTest --- .../server/edge/DeviceEdgeTest.java | 37 ++++++++++++++++++- .../server/edge/imitator/EdgeImitator.java | 7 ++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index 488bd4ed70..458bb3240e 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -711,7 +711,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(30, TimeUnit.SECONDS)); + Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(TIMEOUT, TimeUnit.SECONDS)); Assert.assertEquals(JacksonUtil.newObjectNode().put(attrKey, attrValue), JacksonUtil.fromBytes(onUpdateCallback.getPayloadBytes())); @@ -798,7 +798,21 @@ public class DeviceEdgeTest extends AbstractEdgeTest { // clean up stored edge events edgeEventService.cleanupEvents(1); - // perform rpc call to verify edgeId in DeviceActorMessageProcessor updated properly + // edge is disconnected: perform rpc call - no edge event saved + doPostAsync( + "/api/rpc/oneway/" + device.getId().getId().toString(), + JacksonUtil.toString(createDefaultRpc()), + String.class, + status().isOk()); + Awaitility.await() + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> { + PageData result = edgeEventService.findEdgeEvents(tenantId, tmpEdge.getId(), 0L, null, new TimePageLink(1)); + return result.getTotalElements() == 0; + }); + + // edge is connected: perform rpc call to verify edgeId in DeviceActorMessageProcessor updated properly + simulateEdgeActivation(tmpEdge); doPostAsync( "/api/rpc/oneway/" + device.getId().getId().toString(), JacksonUtil.toString(createDefaultRpc()), @@ -857,4 +871,23 @@ public class DeviceEdgeTest extends AbstractEdgeTest { return rpc; } + + private void simulateEdgeActivation(Edge edge) throws Exception { + ObjectNode attributes = JacksonUtil.newObjectNode(); + attributes.put("active", true); + doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributes); + Awaitility.await() + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> { + List> values = doGetAsyncTyped("/api/plugins/telemetry/EDGE/" + edge.getId() + + "/values/attributes/SERVER_SCOPE", new TypeReference<>() {}); + Optional> activeAttrOpt = values.stream().filter(att -> att.get("key").equals("active")).findFirst(); + if (activeAttrOpt.isEmpty()) { + return false; + } + Map activeAttr = activeAttrOpt.get(); + return "true".equals(activeAttr.get("value").toString()); + }); + } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index f5e018ed3a..916e5790e9 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.thingsboard.edge.rpc.EdgeGrpcClient; import org.thingsboard.edge.rpc.EdgeRpcClient; +import org.thingsboard.server.controller.AbstractWebTest; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; @@ -72,8 +73,6 @@ import java.util.stream.Collectors; @Slf4j public class EdgeImitator { - public static final int TIMEOUT_IN_SECONDS = 30; - private String routingKey; private String routingSecret; @@ -344,7 +343,7 @@ public class EdgeImitator { } public boolean waitForMessages() throws InterruptedException { - return waitForMessages(TIMEOUT_IN_SECONDS); + return waitForMessages(AbstractWebTest.TIMEOUT); } public boolean waitForMessages(int timeoutInSeconds) throws InterruptedException { @@ -359,7 +358,7 @@ public class EdgeImitator { } public boolean waitForResponses() throws InterruptedException { - return responsesLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + return responsesLatch.await(AbstractWebTest.TIMEOUT, TimeUnit.SECONDS); } public void expectResponsesAmount(int messageAmount) { From 360c15710e1dea33b44bccf82a9b4f771e9ebda2 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 9 Feb 2024 13:05:40 +0200 Subject: [PATCH 117/137] UI: Add pl_PL locale --- .../assets/locale/locale.constant-en_US.json | 1 + .../assets/locale/locale.constant-pl_PL.json | 6727 +++++++++++++++++ 2 files changed, 6728 insertions(+) create mode 100644 ui-ngx/src/assets/locale/locale.constant-pl_PL.json diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4160b50024..26521dfa0a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6859,6 +6859,7 @@ "ko_KR": "한국어", "lv_LV": "Latviešu", "nl_BE": "Koninkrijk België", + "pl_PL": "Polski", "pt_BR": "Português do Brasil", "ro_RO": "Română", "sl_SI": "Slovenščina", diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json new file mode 100644 index 0000000000..af6fbb26db --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json @@ -0,0 +1,6727 @@ +{ + "access":{ + "unauthorized":"Unauthorized", + "unauthorized-access":"Unauthorized Access", + "unauthorized-access-text":"You should sign in to have access to this resource!", + "access-forbidden":"Access Forbidden", + "access-forbidden-text":"You haven't access rights to this location!
    Try to sign in with different user if you still wish to gain access to this location.", + "refresh-token-expired":"Session has expired", + "refresh-token-failed":"Unable to refresh session", + "permission-denied":"Permission Denied", + "permission-denied-text":"You don't have permission to perform this operation!" + }, + "account":{ + "account":"Konto", + "notification-settings":"Ustawienia powiadomień" + }, + "action":{ + "activate":"Aktywuj", + "suspend":"Zawieś", + "save":"Zapisz", + "saveAs":"Zapisz jako", + "move":"Przenieś", + "cancel":"Anuluj", + "ok":"OK", + "delete":"Usuń", + "add":"Dodaj", + "yes":"Tak", + "no":"Nie", + "update":"Aktualizuj", + "remove":"Usuń", + "select":"Wybierz", + "search":"Szukaj", + "clear-search":"Wyczyść wyszukiwanie", + "assign":"Przypisz", + "unassign":"Cofnij przypisanie", + "share":"Udostępnij", + "make-private":"Uczyń prywatnym", + "apply":"Zastosuj", + "apply-changes":"Zastosuj zmiany", + "edit-mode":"Tryb edycji", + "enter-edit-mode":"Wejdź w tryb edycji", + "decline-changes":"Odrzuć zmiany", + "decline":"Odrzuć", + "close":"Zamknij", + "back":"Wstecz", + "run":"Uruchom", + "sign-in":"Zaloguj się!", + "edit":"Edytuj", + "view":"Zobacz", + "create":"Utwórz", + "drag":"Przeciągnij", + "refresh":"Odśwież", + "undo":"Cofnij", + "copy":"Kopiuj", + "paste":"Wklej", + "copy-reference":"Kopiuj odniesienie", + "paste-reference":"Wklej odniesienie", + "import":"Importuj", + "export":"Eksportuj", + "share-via":"Udostępnij przez {{provider}}", + "continue":"Kontynuuj", + "discard-changes":"Porzuć zmiany", + "download":"Pobierz", + "next":"Dalej", + "next-with-label":"Następne: {{label}}", + "read-more":"Czytaj więcej", + "hide":"Ukryj", + "done":"Gotowe", + "print":"Drukuj", + "restore":"Przywróć", + "confirm":"Potwierdź", + "more":"Więcej", + "less":"Mniej", + "skip":"Pomiń", + "send":"Wyślij", + "reset":"Resetuj", + "show-more":"Pokaż więcej", + "dont-show-again":"Nie pokazuj ponownie", + "see-documentation":"Zobacz dokumentację", + "clear":"Wyczyść", + "upload":"Prześlij", + "delete-anyway":"Usuń mimo wszystko", + "delete-selected":"Usuń wybrane" + }, + "aggregation":{ + "aggregation":"Agregacja", + "function":"Funkcja agregacji danych", + "limit":"Maksymalna liczba wartości", + "group-interval":"Interwał grupowania", + "min":"Min", + "max":"Maks", + "avg":"Średnia", + "sum":"Suma", + "count":"Liczba", + "none":"Brak" + }, + "admin":{ + "settings":"Ustawienia", + "general":"Ogólne", + "general-settings":"Ustawienia Ogólne", + "home-settings":"Ustawienia Strony Głównej", + "home":"Strona Główna", + "outgoing-mail":"Serwer poczty", + "outgoing-mail-settings":"Ustawienia Serwera Poczty Wychodzącej", + "system-settings":"Ustawienia Systemowe", + "test-mail-sent":"Testowy email został wysłany pomyślnie!", + "base-url":"Podstawowy URL", + "base-url-required":"Podstawowy URL jest wymagany.", + "prohibit-different-url":"Zabroń używania nazwy hosta z nagłówków żądania klienta", + "prohibit-different-url-hint":"Ta opcja powinna być włączona w środowiskach produkcyjnych. Może powodować problemy bezpieczeństwa, gdy jest wyłączona", + "device-connectivity":{ + "device-connectivity":"Łączność urządzenia", + "http-s":"HTTP(s)", + "mqtt-s":"MQTT(s)", + "coap-s":"COAP(s)", + "http":"HTTP", + "https":"HTTPs", + "mqtt":"MQTT", + "mqtts":"MQTTs", + "coap":"COAP", + "coaps":"COAPs", + "hint":"Jeśli pola hosta lub portu są puste, zostanie użyta domyślna wartość protokołu.", + "host":"Host", + "port":"Port", + "port-pattern":"Port musi być dodatnią liczbą całkowitą.", + "port-range":"Port powinien mieścić się w zakresie od 1 do 65535." + }, + "mail-from":"Nadawca", + "mail-from-required":"Nadawca jest wymagany.", + "smtp-protocol":"Protokół SMTP", + "smtp-host":"Host SMTP", + "smtp-host-required":"Host SMTP jest wymagany.", + "smtp-port":"Port SMTP", + "smtp-port-required":"Musisz podać port SMTP.", + "smtp-port-invalid":"To nie wygląda na prawidłowy port SMTP.", + "timeout-msec":"Limit czasu (ms)", + "timeout-required":"Limit czasu jest wymagany.", + "timeout-invalid":"To nie wygląda na prawidłowy limit czasu.", + "enable-tls":"Włącz TLS", + "tls-version":"Wersja TLS", + "enable-proxy":"Włącz proxy", + "proxy-host":"Host proxy", + "proxy-host-required":"Host proxy jest wymagany.", + "proxy-port":"Port proxy", + "proxy-port-required":"Port proxy jest wymagany.", + "proxy-port-range":"Port proxy powinien znajdować się w zakresie od 1 do 65535.", + "proxy-user":"Użytkownik proxy", + "proxy-password":"Hasło proxy", + "change-password":"Zmień hasło", + "send-test-mail":"Wyślij mail testowy", + "sms-provider":"Dostawca SMS", + "sms-provider-settings":"Ustawienia dostawcy SMS", + "sms-provider-type":"Typ dostawcy SMS", + "sms-provider-type-required":"Typ dostawcy SMS jest wymagany.", + "sms-provider-type-aws-sns":"Amazon SNS", + "sms-provider-type-twilio":"Twilio", + "sms-provider-type-smpp":"SMPP", + "aws-access-key-id":"ID klucza dostępu AWS", + "aws-access-key-id-required":"ID klucza dostępu AWS jest wymagane", + "aws-secret-access-key":"Tajny klucz dostępu AWS", + "aws-secret-access-key-required":"Tajny klucz dostępu AWS jest wymagany", + "aws-region":"Region AWS", + "aws-region-required":"Region AWS jest wymagany", + "number-from":"Numer telefonu nadawcy", + "number-from-required":"Numer telefonu nadawcy jest wymagany.", + "number-to":"Numer telefonu odbiorcy", + "number-to-required":"Numer telefonu odbiorcy jest wymagany.", + "phone-number-hint":"Numer telefonu w formacie E.164, np. +19995550123", + "phone-number-hint-twilio":"Numer telefonu w formacie E.164/Numer SID telefonu/SID usługi wiadomości, np. +19995550123/PNXXX/MGXXX", + "phone-number-pattern":"Nieprawidłowy numer telefonu. Powinien być w formacie E.164, np. +19995550123.", + "phone-number-pattern-twilio":"Nieprawidłowy numer telefonu. Powinien być w formacie E.164/Numer SID telefonu/SID usługi wiadomości, np. +19995550123/PNXXX/MGXXX.", + "sms-message":"Wiadomość SMS", + "sms-message-required":"Wiadomość SMS jest wymagana.", + "sms-message-max-length":"Wiadomość SMS nie może być dłuższa niż 1600 znaków", + "twilio-account-sid":"SID konta Twilio", + "twilio-account-sid-required":"SID konta Twilio jest wymagany", + "twilio-account-token":"Token konta Twilio", + "twilio-account-token-required":"Token konta Twilio jest wymagany", + "send-test-sms":"Wyślij testowego SMS-a", + "test-sms-sent":"Testowy SMS został wysłany pomyślnie!", + "security-settings":"Ustawienia bezpieczeństwa", + "password-policy":"Polityka haseł", + "minimum-password-length":"Minimalna długość hasła", + "minimum-password-length-required":"Minimalna długość hasła jest wymagana", + "minimum-password-length-range":"Minimalna długość hasła powinna wynosić od 6 do 50", + "maximum-password-length":"Maksymalna długość hasła", + "maximum-password-length-min":"Maksymalna długość hasła powinna wynosić co najmniej 6", + "maximum-password-length-less-min":"Maksymalna długość hasła powinna być większa niż minimalna długość", + "minimum-uppercase-letters":"Minimalna liczba wielkich liter", + "minimum-uppercase-letters-range":"Minimalna liczba wielkich liter nie może być ujemna", + "minimum-lowercase-letters":"Minimalna liczba małych liter", + "minimum-lowercase-letters-range":"Minimalna liczba małych liter nie może być ujemna", + "minimum-digits":"Minimalna liczba cyfr", + "minimum-digits-range":"Minimalna liczba cyfr nie może być ujemna", + "minimum-special-characters":"Minimalna liczba znaków specjalnych", + "minimum-special-characters-range":"Minimalna liczba znaków specjalnych nie może być ujemna", + "password-expiration-period-days":"Okres ważności hasła w dniach", + "password-expiration-period-days-range":"Okres ważności hasła w dniach nie może być ujemny", + "password-reuse-frequency-days":"Częstotliwość ponownego używania hasła w dniach", + "password-reuse-frequency-days-range":"Częstotliwość ponownego używania hasła w dniach nie może być ujemna", + "allow-whitespace":"Zezwól na białe znaki", + "force-reset-password-if-no-valid":"Wymuś zresetowanie hasła, jeśli nie jest ważne", + "force-reset-password-if-no-valid-hint":"Ostrożnie z włączeniem tej funkcji: będzie wymagać od użytkowników z nieaktualnymi hasłami resetowania ich poprzez email.", + "general-policy":"Ogólna polityka", + "max-failed-login-attempts":"Maksymalna liczba nieudanych prób", + "minimum-max-failed-login-attempts-range":"Maksymalna liczba nieudanych prób logowania nie może być ujemna", + "user-lockout-notification-email":"W przypadku zablokowania konta użytkownika, wyślij powiadomienie na email", + "domain-name":"Nazwa domeny", + "domain-name-unique":"Nazwa domeny i protokół muszą być unikalne.", + "domain-name-max-length":"Nazwa domeny powinna być krótsza niż 256", + "error-verification-url":"Nazwa domeny nie powinna zawierać symboli '/' i ':'. Przykład: thingsboard.io", + "connection-settings":"Ustawienia połączenia", + "oauth2":{ + "access-token-uri":"URI tokenu dostępu", + "access-token-uri-required":"URI tokenu dostępu jest wymagane.", + "activate-user":"Aktywuj użytkownika", + "add-domain":"Dodaj domenę", + "delete-domain":"Usuń domenę", + "add-provider":"Dodaj dostawcę", + "delete-provider":"Usuń dostawcę", + "allow-user-creation":"Zezwalaj na tworzenie użytkowników", + "always-fullscreen":"Zawsze na pełnym ekranie", + "authorization-uri":"URI autoryzacji", + "authorization-uri-required":"URI autoryzacji jest wymagane.", + "client-authentication-method":"Metoda uwierzytelnienia klienta", + "client-id":"ID klienta", + "client-id-required":"ID klienta jest wymagane.", + "client-id-max-length":"ID klienta powinno być krótsze niż 256", + "client-secret":"Sekret klienta", + "client-secret-required":"Sekret klienta jest wymagany.", + "client-secret-max-length":"Sekret klienta powinien być krótszy niż 2049", + "custom-setting":"Niestandardowe ustawienia", + "customer-name-pattern":"Wzorzec nazwy klienta", + "customer-name-pattern-max-length":"Wzorzec nazwy klienta powinien być krótszy niż 256", + "default-dashboard-name":"Domyślna nazwa pulpitu nawigacyjnego", + "default-dashboard-name-max-length":"Domyślna nazwa pulpitu nawigacyjnego powinna być krótsza niż 256", + "delete-domain-text":"Uważaj, po potwierdzeniu domena i wszystkie dane dostawcy będą niedostępne.", + "delete-domain-title":"Czy na pewno chcesz usunąć ustawienia domeny '{{domainName}}'?", + "delete-registration-text":"Uważaj, po potwierdzeniu dane dostawcy będą niedostępne.", + "delete-registration-title":"Czy na pewno chcesz usunąć dostawcę '{{name}}'?", + "email-attribute-key":"Klucz atrybutu email", + "email-attribute-key-required":"Klucz atrybutu email jest wymagany.", + "email-attribute-key-max-length":"Klucz atrybutu email powinien być krótszy niż 32", + "first-name-attribute-key":"Klucz atrybutu imienia", + "first-name-attribute-key-max-length":"Klucz atrybutu imienia powinien być krótszy niż 32", + "general":"Ogólne", + "jwk-set-uri":"URI klucza Web JSON", + "last-name-attribute-key":"Klucz atrybutu nazwiska", + "last-name-attribute-key-max-length":"Klucz atrybutu nazwiska powinien być krótszy niż 32", + "login-button-icon":"Ikona przycisku logowania", + "login-button-label":"Etykieta dostawcy", + "login-button-label-placeholder":"Zaloguj się przez $(Etykieta dostawcy)", + "login-button-label-required":"Etykieta jest wymagana.", + "login-provider":"Dostawca logowania", + "mapper":"Mapper", + "new-domain":"Nowa domena", + "oauth2":"OAuth2", + "password-max-length":"Hasło powinno być krótsze niż 256", + "redirect-uri-template":"Szablon URI przekierowania", + "copy-redirect-uri":"Kopiuj URI przekierowania", + "registration-id":"ID rejestracji", + "registration-id-required":"ID rejestracji jest wymagane.", + "registration-id-unique":"ID rejestracji musi być unikalne dla systemu.", + "scope":"Zakres", + "scope-required":"Zakres jest wymagany.", + "tenant-name-pattern":"Wzorzec nazwy dzierżawcy", + "tenant-name-pattern-required":"Wzorzec nazwy dzierżawcy jest wymagany.", + "tenant-name-pattern-max-length":"Wzorzec nazwy dzierżawcy powinien być krótszy niż 256", + "tenant-name-strategy":"Strategia nazewnictwa dzierżawcy", + "type":"Typ mappera", + "uri-pattern-error":"Nieprawidłowy format URI.", + "url":"URL", + "url-pattern":"Nieprawidłowy format URL.", + "url-required":"URL jest wymagany.", + "url-max-length":"URL powinien być krótszy niż 256", + "user-info-uri":"URI informacji o użytkowniku", + "user-info-uri-required":"URI informacji o użytkowniku jest wymagane.", + "username-max-length":"Nazwa użytkownika powinna być krótsza niż 256", + "user-name-attribute-name":"Klucz atrybutu nazwy użytkownika", + "user-name-attribute-name-required":"Klucz atrybutu nazwy użytkownika jest wymagany", + "protocol":"Protokół", + "domain-schema-http":"HTTP", + "domain-schema-https":"HTTPS", + "domain-schema-mixed":"HTTP+HTTPS", + "enable":"Włącz ustawienia OAuth2", + "domains":"Domeny", + "mobile-apps":"Aplikacje mobilne", + "no-mobile-apps":"Brak skonfigurowanych aplikacji", + "mobile-package":"Pakiet aplikacji", + "mobile-package-placeholder":"Np.: my.example.app", + "mobile-package-hint":"Dla Androida: unikalny ID aplikacji. Dla iOS: identyfikator paczki produktu.", + "mobile-package-unique":"Pakiet aplikacji musi być unikalny.", + "mobile-app-secret":"Sekret aplikacji", + "invalid-mobile-app-secret":"Sekret aplikacji musi zawierać tylko znaki alfanumeryczne i mieć od 16 do 2048 znaków.", + "copy-mobile-app-secret":"Kopiuj sekret aplikacji", + "add-mobile-app":"Dodaj aplikację", + "delete-mobile-app":"Usuń informacje o aplikacji", + "providers":"Dostawcy", + "platform-web":"Web", + "platform-android":"Android", + "platform-ios":"iOS", + "all-platforms":"Wszystkie platformy", + "smtp-provider":"Dostawca SMTP", + "allowed-platforms":"Dozwolone platformy", + "authentication":"Autentykacja", + "basic":"Podstawowy", + "provider":"Dostawca", + "redirect-url":"URI przekierowania", + "domain-name":"Nazwa domeny", + "redirect-url-template":"Szablon URI przekierowania", + "microsoft-tenant-id":"ID dzierżawy (tenant) Microsoft", + "microsoft-tenant-id-required":"ID dzierżawy (tenant) Microsoft jest wymagane", + "token-uri":"URI tokenu", + "token-uri-required":"URI tokenu jest wymagane", + "redirect-uri":"URI przekierowania", + "google-provider":"Google", + "microsoft-provider":"Office 365", + "sendgrid-provider":"Sendgrid", + "custom-provider":"Niestandardowy", + "generate-access-token":"Generuj token dostępu", + "update-access-token":"Aktualizuj token dostępu", + "access-token-status":"Status tokenu dostępu:", + "token-status-generated":"wygenerowany", + "token-status-not-generated":"nie wygenerowany" + }, + "smpp-provider":{ + "smpp-version":"Wersja SMPP", + "smpp-host":"Host SMPP", + "smpp-host-required":"Host SMPP jest wymagany", + "smpp-port":"Port SMPP", + "smpp-port-required":"Port SMPP jest wymagany", + "system-id":"ID systemu", + "system-id-required":"ID systemu jest wymagane", + "password":"Hasło", + "password-required":"Hasło jest wymagane", + "type-settings":"Ustawienia typu", + "source-settings":"Ustawienia źródła", + "destination-settings":"Ustawienia docelowe", + "additional-settings":"Dodatkowe ustawienia", + "system-type":"Typ systemu", + "bind-type":"Typ powiązania", + "service-type":"Typ usługi", + "source-address":"Adres źródłowy", + "source-ton":"TON źródłowy", + "source-npi":"NPI źródłowy", + "destination-ton":"TON docelowy (Typ Numeru)", + "destination-npi":"NPI docelowy (Identyfikacja Planu Numeracji)", + "address-range":"Zakres adresów", + "coding-scheme":"Schemat kodowania", + "bind-type-tx":"Nadajnik", + "bind-type-rx":"Odbiornik", + "bind-type-trx":"Nadawczo-odbiorczy", + "ton-unknown":"Nieznany", + "ton-international":"Międzynarodowy", + "ton-national":"Krajowy", + "ton-network-specific":"Specyficzny dla sieci", + "ton-subscriber-number":"Numer abonenta", + "ton-alphanumeric":"Alfanumeryczny", + "ton-abbreviated":"Skrócony", + "npi-unknown":"0 - Nieznany", + "npi-isdn":"1 - ISDN/plan numeracji telefonicznej (E163/E164)", + "npi-data-numbering-plan":"3 - Plan numeracji danych (X.121)", + "npi-telex-numbering-plan":"4 - Plan numeracji telexu (F.69)", + "npi-land-mobile":"6 - Mobilny lądowy (E.212)", + "npi-national-numbering-plan":"8 - Krajowy plan numeracji", + "npi-private-numbering-plan":"9 - Prywatny plan numeracji", + "npi-ermes-numbering-plan":"10 - Plan numeracji ERMES (ETSI DE/PS 3 01-3)", + "npi-internet":"13 - Internet (IP)", + "npi-wap-client-id":"18 - Identyfikator klienta WAP (do zdefiniowania przez Forum WAP)", + "scheme-smsc":"0 - Domyślny alfabet SMSC (ASCII dla krótkiego i długiego kodu oraz do GSM dla bezpłatnych)", + "scheme-ia5":"1 - IA5 (ASCII dla krótkiego i długiego kodu, łaciński 9 dla bezpłatnych (ISO-8859-9))", + "scheme-octet-unspecified-2":"2 - Oktet nieokreślony (binarny 8-bitowy)", + "scheme-latin-1":"3 - Łaciński 1 (ISO-8859-1)", + "scheme-octet-unspecified-4":"4 - Oktet nieokreślony (binarny 8-bitowy)", + "scheme-jis":"5 - JIS (X 0208-1990)", + "scheme-cyrillic":"6 - Cyrylica (ISO-8859-5)", + "scheme-latin-hebrew":"7 - Łaciński/hebrajski (ISO-8859-8)", + "scheme-ucs-utf":"8 - UCS2/UTF-16 (ISO/IEC-10646)", + "scheme-pictogram-encoding":"9 - Kodowanie piktogramów", + "scheme-music-codes":"10 - Kody muzyczne (ISO-2022-JP)", + "scheme-extended-kanji-jis":"13 - Rozszerzony Kanji JIS (X 0212-1990)", + "scheme-korean-graphic-character-set":"14 - Zestaw koreańskich znaków graficznych (KS C 5601/KS X 1001)" + }, + "queue-select-name":"Wybierz nazwę kolejki", + "queue-name":"Nazwa", + "queue-name-required":"Nazwa kolejki jest wymagana!", + "queues":"Kolejki", + "queue-partitions":"Partycje", + "queue-submit-strategy":"Strategia zgłaszania", + "queue-processing-strategy":"Strategia przetwarzania", + "queue-configuration":"Konfiguracja kolejki", + "repository-settings":"Ustawienia repozytorium", + "repository":"Repozytorium", + "repository-url":"URL repozytorium", + "repository-url-required":"URL repozytorium jest wymagany.", + "default-branch":"Domyślna nazwa gałęzi", + "repository-read-only":"Tylko do odczytu", + "show-merge-commits":"Pokaż commity scalające", + "authentication-settings":"Ustawienia autentykacji", + "auth-method":"Metoda uwierzytelniania", + "auth-method-username-password":"Hasło / token dostępu", + "auth-method-username-password-hint":"Użytkownicy GitHub muszą używać tokenów dostępu z uprawnieniami do zapisu w repozytorium.", + "auth-method-private-key":"Klucz prywatny", + "password-access-token":"Hasło / token dostępu", + "change-password-access-token":"Zmień hasło / token dostępu", + "private-key":"Klucz prywatny", + "drop-private-key-file-or":"Przeciągnij i upuść plik klucza prywatnego lub", + "passphrase":"Hasło", + "enter-passphrase":"Wprowadź hasło", + "change-passphrase":"Zmień hasło", + "check-access":"Sprawdź dostęp", + "check-repository-access-success":"Dostęp do repozytorium pomyślnie zweryfikowany!", + "delete-repository-settings-title":"Czy na pewno chcesz usunąć ustawienia repozytorium?", + "delete-repository-settings-text":"Uważaj, po potwierdzeniu ustawienia repozytorium zostaną usunięte, a funkcja kontroli wersji będzie niedostępna.", + "auto-commit-settings":"Ustawienia automatycznego zatwierdzania", + "auto-commit":"Automatyczne zatwierdzanie", + "auto-commit-entities":"Automatyczne zatwierdzanie encji", + "no-auto-commit-entities-prompt":"Brak skonfigurowanych encji do automatycznego zatwierdzania", + "delete-auto-commit-settings-title":"Czy na pewno chcesz usunąć ustawienia automatycznego zatwierdzania?", + "delete-auto-commit-settings-text":"Uważaj, po potwierdzeniu ustawienia automatycznego zatwierdzania zostaną usunięte, a automatyczne zatwierdzanie zostanie wyłączone dla wszystkich encji.", + "2fa":{ + "2fa":"Dwuetapowa autentykacja", + "available-providers":"Dostępni dostawcy", + "issuer-name":"Nazwa wydawcy", + "issuer-name-required":"Nazwa wydawcy jest wymagana.", + "max-verification-failures-before-user-lockout":"Maksymalna liczba nieudanych weryfikacji przed zablokowaniem użytkownika", + "max-verification-failures-before-user-lockout-pattern":"Maksymalna liczba nieudanych weryfikacji musi być dodatnią liczbą całkowitą.", + "number-of-checking-attempts":"Liczba prób sprawdzenia", + "number-of-checking-attempts-pattern":"Liczba prób sprawdzenia musi być dodatnią liczbą całkowitą.", + "number-of-checking-attempts-required":"Liczba prób sprawdzenia jest wymagana.", + "number-of-codes":"Liczba kodów", + "number-of-codes-pattern":"Liczba kodów musi być dodatnią liczbą całkowitą.", + "number-of-codes-required":"Liczba kodów jest wymagana.", + "provider":"Dostawca", + "retry-verification-code-period":"Okres ponownego próbowania kodu weryfikacyjnego (sek)", + "retry-verification-code-period-pattern":"Minimalny czas okresu to 5 sek", + "retry-verification-code-period-required":"Okres ponownego próbowania kodu weryfikacyjnego jest wymagany.", + "total-allowed-time-for-verification":"Całkowity dozwolony czas na weryfikację (sek)", + "total-allowed-time-for-verification-pattern":"Minimalny całkowity dozwolony czas to 60 sek", + "total-allowed-time-for-verification-required":"Całkowity dozwolony czas jest wymagany.", + "use-system-two-factor-auth-settings":"Użyj systemowych ustawień dwuetapowej autentykacji", + "verification-code-check-rate-limit":"Limit częstotliwości sprawdzania kodu weryfikacyjnego", + "verification-code-lifetime":"Czas życia kodu weryfikacyjnego (sek)", + "verification-code-lifetime-pattern":"Czas życia kodu weryfikacyjnego musi być dodatnią liczbą całkowitą.", + "verification-code-lifetime-required":"Czas życia kodu weryfikacyjnego jest wymagany.", + "verification-message-template":"Szablon wiadomości weryfikacyjnej", + "verification-limitations":"Ograniczenia weryfikacji", + "verification-message-template-pattern":"Wiadomość weryfikacyjna musi zawierać wzór: ${code}", + "verification-message-template-required":"Szablon wiadomości weryfikacyjnej jest wymagany.", + "within-time":"W czasie (sek)", + "within-time-pattern":"Czas musi być dodatnią liczbą całkowitą.", + "within-time-required":"Czas jest wymagany." + }, + "jwt":{ + "security-settings":"Ustawienia bezpieczeństwa JWT", + "issuer-name":"Nazwa wydawcy", + "issuer-name-required":"Nazwa wydawcy jest wymagana.", + "signings-key":"Klucz podpisujący", + "signings-key-hint":"Kodowany Base64 ciąg reprezentujący co najmniej 256 bitów danych.", + "signings-key-required":"Klucz podpisujący jest wymagany.", + "signings-key-min-length":"Klucz podpisujący musi reprezentować co najmniej 256 bitów danych.", + "signings-key-base64":"Klucz podpisujący musi być w formacie base64.", + "expiration-time":"Czas wygaśnięcia tokena (sek)", + "expiration-time-required":"Czas wygaśnięcia tokena jest wymagany.", + "expiration-time-pattern":"Czas wygaśnięcia tokena musi być dodatnią liczbą całkowitą.", + "expiration-time-min":"Minimalny czas to 60 sekund (1 minuta).", + "refresh-expiration-time":"Czas wygaśnięcia tokena odświeżającego (sek)", + "refresh-expiration-time-required":"Czas wygaśnięcia tokena odświeżającego jest wymagany.", + "refresh-expiration-time-pattern":"Czas wygaśnięcia tokena odświeżającego musi być dodatnią liczbą całkowitą.", + "refresh-expiration-time-min":"Minimalny czas to 900 sekund (15 minut).", + "refresh-expiration-time-less-token":"Czas tokena odświeżającego musi być dłuższy niż czas tokena.", + "generate-key":"Wygeneruj klucz", + "info-header":"Wszyscy użytkownicy będą musieli ponownie się zalogować", + "info-message":"Zmiana klucza podpisującego JWT spowoduje unieważnienie wszystkich wydanych tokenów. Wszyscy użytkownicy będą musieli ponownie się zalogować. Dotyczy to również skryptów korzystających z Rest API/Websockets." + }, + "resources":"Zasoby", + "notifications":"Powiadomienia", + "notifications-settings":"Ustawienia powiadomień", + "slack-api-token":"Token API Slack", + "slack":"Slack", + "slack-settings":"Ustawienia Slack" + }, + "alarm":{ + "alarm":"Alarm", + "alarms":"Alarmy", + "all-alarms":"Wszystkie alarmy", + "select-alarm":"Wybierz alarm", + "no-alarms-matching":"Nie znaleziono alarmów pasujących do '{{entity}}'.", + "alarm-required":"Alarm jest wymagany", + "alarm-filter":"Filtr alarmów", + "filter":"Filtr", + "alarm-status":"Status alarmu", + "alarm-status-list":"Lista statusów alarmów", + "any-status":"Dowolny status", + "search-status":{ + "ANY":"Dowolny", + "ACTIVE":"Aktywny", + "CLEARED":"Wyczyszczony", + "ACK":"Potwierdzony", + "UNACK":"Niepotwierdzony" + }, + "display-status":{ + "ACTIVE_UNACK":"Aktywny Niepotwierdzony", + "ACTIVE_ACK":"Aktywny Potwierdzony", + "CLEARED_UNACK":"Wyczyszczony Niepotwierdzony", + "CLEARED_ACK":"Wyczyszczony Potwierdzony" + }, + "no-alarms-prompt":"Nie znaleziono alarmów", + "created-time":"Czas utworzenia", + "type":"Typ", + "severity":"Waga", + "originator":"Inicjator", + "originator-type":"Typ inicjatora", + "details":"Szczegóły", + "originator-label":"Etykieta inicjatora", + "assign":"Przypisz", + "assignments":"Przypisania", + "assignee":"Przypisany", + "assignee-id":"ID przypisanego", + "assignee-first-name":"Imię przypisanego", + "assignee-last-name":"Nazwisko przypisanego", + "assignee-email":"Email przypisanego", + "unassigned":"Nieprzypisany", + "assignee-not-set":"Wszystkie", + "status":"Status", + "alarm-details":"Szczegóły alarmu", + "start-time":"Czas rozpoczęcia", + "assign-time":"Czas przypisania", + "end-time":"Czas zakończenia", + "ack-time":"Czas potwierdzenia", + "clear-time":"Czas wyczyszczenia", + "duration":"Czas trwania", + "alarm-severity-list":"Lista ważności alarmów", + "any-severity":"Dowolna ważność", + "severity-critical":"Krytyczna", + "severity-major":"Poważna", + "severity-minor":"Mniejsza", + "severity-warning":"Ostrzeżenie", + "severity-indeterminate":"Nieokreślona", + "acknowledge":"Potwierdź", + "clear":"Wyczyść", + "delete":"Usuń", + "search":"Szukaj alarmów", + "selected-alarms":"{ count, plural, =1 {1 alarm} other {# alarmów} } wybrano", + "no-data":"Brak danych do wyświetlenia", + "polling-interval":"Interwał odpytywania alarmów (sek)", + "polling-interval-required":"Interwał odpytywania alarmów jest wymagany.", + "min-polling-interval-message":"Dozwolony jest minimalny interwał odpytywania 1 sek.", + "aknowledge-alarms-title":"Potwierdź { count, plural, =1 {1 alarm} other {# alarmów} }", + "aknowledge-alarms-text":"Czy na pewno chcesz potwierdzić { count, plural, =1 {1 alarm} other {# alarmów} }?", + "aknowledge-alarm-title":"Potwierdź alarm", + "aknowledge-alarm-text":"Czy na pewno chcesz potwierdzić alarm?", + "selected-alarms-are-acknowledged":"Wybrane alarmy zostały już potwierdzone", + "clear-alarms-title":"Wyczyść { count, plural, =1 {1 alarm} other {# alarmów} }", + "clear-alarms-text":"Czy na pewno chcesz wyczyścić { count, plural, =1 {1 alarm} other {# alarmów} }?", + "clear-alarm-title":"Wyczyść alarm", + "clear-alarm-text":"Czy na pewno chcesz wyczyścić alarm?", + "delete-alarms-title":"Usuń { count, plural, =1 {1 alarm} other {# alarmów} }", + "delete-alarms-text":"Czy na pewno chcesz usunąć { count, plural, =1 {1 alarm} other {# alarmów} }?", + "selected-alarms-are-cleared":"Wybrane alarmy zostały już wyczyszczone", + "alarm-status-filter":"Filtr statusu alarmów", + "alarm-filter-title":"Filtr alarmów", + "assigned":"Przypisane", + "filter-title":"Filtr", + "max-count-load":"Maksymalna liczba alarmów do załadowania (0 - bez limitu)", + "max-count-load-required":"Maksymalna liczba alarmów do załadowania jest wymagana.", + "max-count-load-error-min":"Minimalna wartość to 0.", + "fetch-size":"Rozmiar pobrania", + "fetch-size-required":"Rozmiar pobrania jest wymagany.", + "fetch-size-error-min":"Minimalna wartość to 10.", + "alarm-type-list":"Lista typów alarmów", + "any-type":"Dowolny typ", + "assigned-to-current-user":"Przypisane do aktualnego użytkownika", + "assigned-to-me":"Przypisane do mnie", + "search-propagated-alarms":"Szukaj propagowanych alarmów", + "comments":"Komentarze do alarmu", + "show-more":"Pokaż więcej", + "additional-info":"Dodatkowe informacje", + "alarm-type":"Typ alarmu", + "enter-alarm-type":"Wpisz typ alarmu", + "no-alarm-types-matching":"Nie znaleziono typów alarmów pasujących do '{{entitySubtype}}'.", + "alarm-type-list-empty":"Nie wybrano typów alarmów." + }, + "alarm-activity":{ + "add":"Dodaj komentarz...", + "alarm-comment":"Komentarz do alarmu", + "comments":"Komentarze", + "delete-alarm-comment":"Czy chcesz usunąć ten komentarz?", + "refresh":"Odśwież", + "oldest-first":"Najstarsze najpierw", + "newest-first":"Najnowsze najpierw", + "activity":"Aktywność", + "export":"Eksportuj do CSV", + "author":"Autor", + "created-date":"Data utworzenia", + "edited-date":"Data edycji", + "text":"Tekst", + "system":"System" + }, + "alias":{ + "add":"Dodaj alias", + "edit":"Edytuj alias", + "name":"Nazwa aliasu", + "name-required":"Nazwa aliasu jest wymagana", + "duplicate-alias":"Alias o tej samej nazwie już istnieje.", + "filter-type-single-entity":"Pojedyncza encja", + "filter-type-entity-list":"Lista encji", + "filter-type-entity-name":"Nazwa encji", + "filter-type-entity-type":"Typ encji", + "filter-type-state-entity":"Encja ze stanu pulpitu nawigacyjnego", + "filter-type-state-entity-description":"Encja pobrana z parametrów stanu pulpitu nawigacyjnego", + "filter-type-asset-type":"Typ aktywa", + "filter-type-asset-type-description":"Aktywa typu '{{assetTypes}}'", + "filter-type-asset-type-and-name-description":"Aktywa typu '{{assetTypes}}' o nazwie rozpoczynającej się od '{{prefix}}'", + "filter-type-device-type":"Typ urządzenia", + "filter-type-device-type-description":"Urządzenia typu '{{deviceTypes}}'", + "filter-type-device-type-and-name-description":"Urządzenia typu '{{deviceTypes}}' o nazwie rozpoczynającej się od '{{prefix}}'", + "filter-type-entity-view-type":"Typ widoku encji", + "filter-type-entity-view-type-description":"Widoki encji typu '{{entityViewTypes}}'", + "filter-type-entity-view-type-and-name-description":"Widoki encji typu '{{entityViewTypes}}' o nazwie rozpoczynającej się od '{{prefix}}'", + "filter-type-edge-type":"Typ krawędzi", + "filter-type-edge-type-description":"Krawędzie typu '{{edgeTypes}}'", + "filter-type-edge-type-and-name-description":"Krawędzie typu '{{edgeTypes}}' o nazwie rozpoczynającej się od '{{prefix}}'", + "filter-type-relations-query":"Zapytanie o relacje", + "filter-type-relations-query-description":"{{entities}} mające relację typu {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query":"Zapytanie wyszukiwania aktywów", + "filter-type-asset-search-query-description":"Aktywa typu {{assetTypes}} mające relację typu {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query":"Zapytanie wyszukiwania urządzeń", + "filter-type-device-search-query-description":"Urządzenia typu {{deviceTypes}} mające relację typu {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query":"Zapytanie wyszukiwania widoków encji", + "filter-type-entity-view-search-query-description":"Widoki encji typu {{entityViewTypes}} mające relację typu {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-apiUsageState":"Stan użycia API", + "filter-type-edge-search-query":"Zapytanie wyszukiwania krawędzi", + "filter-type-edge-search-query-description":"Krawędzie typu {{edgeTypes}} mające relację typu {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter":"Filtr encji", + "resolve-multiple":"Rozwiąż jako wiele encji", + "filter-type":"Typ filtra", + "filter-type-required":"Typ filtra jest wymagany.", + "entity-filter-no-entity-matched":"Nie znaleziono encji pasujących do określonego filtra.", + "no-entity-filter-specified":"Nie określono filtra encji", + "root-state-entity":"Użyj encji stanu pulpitu nawigacyjnego jako korzenia", + "last-level-relation":"Pobierz tylko relacje ostatniego poziomu", + "root-entity":"Encja główna", + "state-entity-parameter-name":"Nazwa parametru encji stanu", + "default-state-entity":"Domyślna encja stanu", + "default-entity-parameter-name":"Domyślnie", + "max-relation-level":"Maksymalny poziom relacji", + "unlimited-level":"Nieograniczony poziom", + "state-entity":"Encja stanu pulpitu nawigacyjnego", + "all-entities":"Wszystkie encje", + "any-relation":"dowolna" + }, + "asset":{ + "asset":"Aktywa", + "assets":"Aktywa", + "management":"Zarządzanie aktywami", + "view-assets":"Wyświetl aktywa", + "add":"Dodaj aktywa", + "asset-type-max-length":"Typ aktywa powinien być krótszy niż 256", + "assign-to-customer":"Przypisz do klienta", + "assign-asset-to-customer":"Przypisz aktywa do klienta", + "assign-asset-to-customer-text":"Proszę wybrać aktywa do przypisania klientowi", + "no-assets-text":"Nie znaleziono aktywów", + "assign-to-customer-text":"Proszę wybrać klienta do przypisania aktywów", + "public":"Publiczne", + "assignedToCustomer":"Przypisane do klienta", + "make-public":"Upublicznij aktywa", + "make-private":"Prywatyzuj aktywa", + "unassign-from-customer":"Usuń przypisanie od klienta", + "delete":"Usuń aktywa", + "asset-public":"Aktywa są publiczne", + "asset-type":"Typ aktywa", + "asset-type-required":"Typ aktywa jest wymagany.", + "select-asset-type":"Wybierz typ aktywa", + "enter-asset-type":"Wprowadź typ profilu aktywa", + "any-asset":"Dowolne aktywa", + "no-asset-types-matching":"Nie znaleziono typów aktywów pasujących do '{{entitySubtype}}'.", + "asset-type-list-empty":"Nie wybrano typów aktywów.", + "asset-types":"Typy aktywów", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "name-max-length":"Nazwa powinna być krótsza niż 256", + "label-max-length":"Etykieta powinna być krótsza niż 256", + "description":"Opis", + "type":"Typ", + "type-required":"Typ jest wymagany.", + "details":"Szczegóły", + "events":"Wydarzenia", + "add-asset-text":"Dodaj nowe aktywa", + "asset-details":"Szczegóły aktywa", + "assign-assets":"Przypisz aktywa", + "assign-assets-text":"Przypisz { count, plural, =1 {1 aktywa} other {# aktywów} } do klienta", + "assign-asset-to-edge-title":"Przypisz aktywa do krawędzi", + "assign-asset-to-edge-text":"Proszę wybrać aktywa do przypisania do krawędzi", + "delete-assets":"Usuń aktywa", + "unassign-assets":"Usuń przypisanie aktywów", + "unassign-assets-action-title":"Usuń przypisanie { count, plural, =1 {1 aktywa} other {# aktywów} } od klienta", + "assign-new-asset":"Przypisz nowe aktywa", + "delete-asset-title":"Czy na pewno chcesz usunąć aktywa '{{assetName}}'?", + "delete-asset-text":"Ostrożnie, po potwierdzeniu aktywa oraz wszystkie powiązane dane staną się nieodwracalne.", + "delete-assets-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 aktywa} other {# aktywów} }?", + "delete-assets-action-title":"Usuń { count, plural, =1 {1 aktywa} other {# aktywów} }", + "delete-assets-text":"Ostrożnie, po potwierdzeniu wszystkie wybrane aktywa oraz wszystkie powiązane dane staną się nieodwracalne.", + "make-public-asset-title":"Czy na pewno chcesz uczynić aktywa '{{assetName}}' publicznymi?", + "make-public-asset-text":"Po potwierdzeniu aktywa oraz wszystkie jego dane staną się publicznie dostępne.", + "make-private-asset-title":"Czy na pewno chcesz uczynić aktywa '{{assetName}}' prywatnymi?", + "make-private-asset-text":"Po potwierdzeniu aktywa oraz wszystkie jego dane staną się prywatne i niedostępne dla innych.", + "unassign-asset-title":"Czy na pewno chcesz usunąć przypisanie aktywów '{{assetName}}'?", + "unassign-asset-text":"Po potwierdzeniu aktywa zostaną usunięte z przypisania i nie będą dostępne dla klienta.", + "unassign-asset":"Usuń przypisanie aktywów", + "unassign-assets-title":"Czy na pewno chcesz usunąć przypisanie { count, plural, =1 {1 aktywa} other {# aktywów} }?", + "unassign-assets-text":"Po potwierdzeniu wszystkie wybrane aktywa zostaną usunięte z przypisania i nie będą dostępne dla klienta.", + "unassign-assets-from-edge":"Usuń przypisanie aktywów od krawędzi", + "copyId":"Kopiuj ID aktywów", + "idCopiedMessage":"ID aktywów zostało skopiowane do schowka", + "select-asset":"Wybierz aktywa", + "no-assets-matching":"Nie znaleziono aktywów pasujących do '{{entity}}'.", + "asset-required":"Aktywa są wymagane", + "name-starts-with":"Wyrażenie nazwy aktywów", + "help-text":"Użyj '%', zgodnie z potrzebą: '%nazwa_zawiera_aktywa%', '%nazwa_kończy_się_aktywami', 'nazwa_rozpoczyna_się_aktywami'.", + "import":"Importuj aktywa", + "asset-file":"Plik aktywów", + "label":"Etykieta", + "search":"Wyszukaj aktywa", + "assign-asset-to-edge":"Przypisz aktywa do krawędzi", + "unassign-asset-from-edge":"Usuń przypisanie aktywów od krawędzi", + "unassign-asset-from-edge-title":"Czy na pewno chcesz usunąć przypisanie aktywów '{{assetName}}' od krawędzi?", + "unassign-asset-from-edge-text":"Po potwierdzeniu aktywa zostaną usunięte z przypisania od krawędzi i nie będą dostępne dla tej krawędzi.", + "unassign-assets-from-edge-title":"Czy na pewno chcesz usunąć przypisanie { count, plural, =1 {1 aktywa} other {# aktywów} } od krawędzi?", + "unassign-assets-from-edge-text":"Po potwierdzeniu wszystkie wybrane aktywa zostaną usunięte z przypisania od krawędzi i nie będą dostępne dla tej krawędzi.", + "selected-assets":"{ count, plural, =1 {1 aktywo} other {# aktywów} } wybrane" + }, + "attribute":{ + "attributes":"Atrybuty", + "latest-telemetry":"Najnowsza telemetria", + "no-latest-telemetry":"Brak najnowszej telemetrii", + "attributes-scope":"Zakres atrybutów encji", + "scope-telemetry":"Telemetria", + "scope-latest-telemetry":"Najnowsza telemetria", + "scope-client":"Atrybuty klienta", + "scope-server":"Atrybuty serwera", + "scope-shared":"Atrybuty współdzielone", + "add":"Dodaj atrybut", + "key":"Klucz", + "key-max-length":"Klucz powinien być krótszy niż 256", + "last-update-time":"Czas ostatniej aktualizacji", + "key-required":"Klucz atrybutu jest wymagany.", + "value":"Wartość", + "value-required":"Wartość atrybutu jest wymagana.", + "telemetry-key-required":"Klucz telemetrii jest wymagany", + "telemetry-value-required":"Wartość telemetrii jest wymagana", + "delete-attributes-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 atrybut} other {# atrybutów} }?", + "delete-attributes-text":"Ostrożnie, po potwierdzeniu wszystkie wybrane atrybuty zostaną usunięte.", + "delete-attributes":"Usuń atrybuty", + "enter-attribute-value":"Wprowadź wartość atrybutu", + "show-on-widget":"Pokaż na widżecie", + "widget-mode":"Tryb widżetu", + "next-widget":"Następny widżet", + "prev-widget":"Poprzedni widżet", + "add-to-dashboard":"Dodaj do pulpitu nawigacyjnego", + "add-widget-to-dashboard":"Dodaj widżet do pulpitu nawigacyjnego", + "selected-attributes":"{ count, plural, =1 {1 atrybut} other {# atrybutów} } wybrano", + "selected-telemetry":"{ count, plural, =1 {1 jednostka telemetrii} other {# jednostek telemetrii} } wybrano", + "no-attributes-text":"Nie znaleziono atrybutów", + "no-telemetry-text":"Nie znaleziono telemetrii", + "copy-key":"Kopiuj klucz", + "add-telemetry":"Dodaj telemetrię", + "copy-value":"Kopiuj wartość", + "delete-timeseries":{ + "start-time":"Czas rozpoczęcia", + "ends-on":"Kończy się", + "strategy":"Strategia", + "delete-strategy":"Strategia usuwania", + "all-data":"Usuń wszystkie dane", + "all-data-except-latest-value":"Usuń wszystkie dane oprócz najnowszej wartości", + "latest-value":"Usuń najnowszą wartość", + "all-data-for-time-period":"Usuń wszystkie dane za okres czasu", + "rewrite-latest-value":"Zastąp najnowszą wartość" + } + }, + "api-usage":{ + "api-features":"Funkcje API", + "api-usage":"Użycie API", + "alarm":"Alarm", + "alarms-created":"Utworzone alarmy", + "alarms-created-daily-activity":"Dzienna aktywność utworzonych alarmów", + "alarms-created-hourly-activity":"Godzinna aktywność utworzonych alarmów", + "alarms-created-monthly-activity":"Miesięczna aktywność utworzonych alarmów", + "data-points":"Punkty danych", + "data-points-storage-days":"Dni przechowywania punktów danych", + "device-api":"API urządzenia", + "email":"Email", + "email-messages":"Wiadomości e-mail", + "email-messages-daily-activity":"Dzienna aktywność wiadomości e-mail", + "email-messages-monthly-activity":"Miesięczna aktywność wiadomości e-mail", + "exceptions":"Wyjątki", + "executions":"Wykonania", + "scripts":"Skrypty", + "scripts-hourly-activity":"Godzinna aktywność skryptów", + "scripts-daily-activity":"Dzienna aktywność skryptów", + "scripts-monthly-activity":"Miesięczna aktywność skryptów", + "javascript":"JavaScript", + "javascript-executions":"Wykonania JavaScript", + "tbel":"TBEL", + "tbel-executions":"Wykonania TBEL", + "latest-error":"Ostatni błąd", + "messages":"Wiadomości", + "notifications":"Powiadomienia", + "notifications-email-sms":"Powiadomienia (Email/SMS)", + "notifications-hourly-activity":"Godzinna aktywność powiadomień", + "permanent-failures":"Stałe awarie ${entityName}", + "permanent-timeouts":"Stałe przekroczenia czasu ${entityName}", + "processing-failures":"Błędy przetwarzania ${entityName}", + "processing-failures-and-timeouts":"Błędy przetwarzania i przekroczenia czasu", + "processing-timeouts":"Przekroczenia czasu przetwarzania ${entityName}", + "queue-stats":"Statystyki kolejki", + "rule-chain":"Łańcuch reguł", + "rule-engine":"Silnik reguł", + "rule-engine-daily-activity":"Dzienna aktywność silnika reguł", + "rule-engine-executions":"Wykonania silnika reguł", + "rule-engine-hourly-activity":"Godzinna aktywność silnika reguł", + "rule-engine-monthly-activity":"Miesięczna aktywność silnika reguł", + "rule-engine-statistics":"Statystyki silnika reguł", + "rule-node":"Węzeł reguł", + "sms":"SMS", + "sms-messages":"Wiadomości SMS", + "sms-messages-daily-activity":"Dzienna aktywność wiadomości SMS", + "sms-messages-monthly-activity":"Miesięczna aktywność wiadomości SMS", + "successful":"Udane ${entityName}", + "telemetry":"Telemetria", + "telemetry-persistence":"Przechowywanie telemetrii", + "telemetry-persistence-daily-activity":"Dzienna aktywność przechowywania telemetrii", + "telemetry-persistence-hourly-activity":"Godzinna aktywność przechowywania telemetrii", + "telemetry-persistence-monthly-activity":"Miesięczna aktywność przechowywania telemetrii", + "transport":"Transport", + "transport-daily-activity":"Dzienna aktywność transportu", + "transport-data-points":"Punkty danych transportu", + "transport-hourly-activity":"Godzinna aktywność transportu", + "transport-messages":"Wiadomości transportu", + "transport-monthly-activity":"Miesięczna aktywność transportu", + "view-details":"Wyświetl szczegóły", + "view-statistics":"Wyświetl statystyki" + }, + "api-limit":{ + "cassandra-queries":"Zapytania do Cassandry", + "entity-version-creation":"Tworzenie wersji encji", + "entity-version-load":"Ładowanie wersji encji", + "notification-requests":"Żądania powiadomień", + "notification-requests-per-rule":"Żądania powiadomień na regułę", + "rest-api-requests":"Żądania REST API", + "rest-api-requests-per-customer":"Żądania REST API na klienta", + "transport-messages":"Wiadomości transportowe", + "transport-messages-per-device":"Wiadomości transportowe na urządzenie", + "ws-updates-per-session":"Aktualizacje WS na sesję" + }, + "audit-log":{ + "audit":"Audyt", + "audit-logs":"Dzienniki audytu", + "timestamp":"Znacznik czasu", + "entity-type":"Typ encji", + "entity-name":"Nazwa encji", + "user":"Użytkownik", + "type":"Typ", + "status":"Status", + "details":"Szczegóły", + "type-added":"Dodano", + "type-deleted":"Usunięto", + "type-updated":"Zaktualizowano", + "type-attributes-updated":"Zaktualizowano atrybuty", + "type-attributes-deleted":"Usunięto atrybuty", + "type-rpc-call":"Wywołanie RPC", + "type-credentials-updated":"Zaktualizowano dane uwierzytelniające", + "type-assigned-to-customer":"Przypisano do klienta", + "type-unassigned-from-customer":"Usunięto przypisanie od klienta", + "type-assigned-to-edge":"Przypisano do krawędzi", + "type-unassigned-from-edge":"Usunięto przypisanie od krawędzi", + "type-activated":"Aktywowano", + "type-suspended":"Zawieszono", + "type-credentials-read":"Odczytano dane uwierzytelniające", + "type-attributes-read":"Odczytano atrybuty", + "type-relation-add-or-update":"Zaktualizowano relację", + "type-relation-delete":"Usunięto relację", + "type-relations-delete":"Usunięto wszystkie relacje", + "type-alarm-ack":"Potwierdzono", + "type-alarm-clear":"Wyczyszczono", + "type-alarm-assign":"Przypisano", + "type-alarm-unassign":"Usunięto przypisanie", + "type-added-comment":"Dodano komentarz", + "type-updated-comment":"Zaktualizowano komentarz", + "type-deleted-comment":"Usunięto komentarz", + "type-login":"Logowanie", + "type-logout":"Wylogowanie", + "type-lockout":"Blokada", + "status-success":"Sukces", + "status-failure":"Niepowodzenie", + "audit-log-details":"Szczegóły dziennika audytu", + "no-audit-logs-prompt":"Nie znaleziono dzienników", + "action-data":"Dane akcji", + "failure-details":"Szczegóły niepowodzenia", + "search":"Szukaj w dziennikach audytu", + "clear-search":"Wyczyść wyszukiwanie", + "type-assigned-from-tenant":"Przypisano od dzierżawcy", + "type-assigned-to-tenant":"Przypisano do dzierżawcy", + "type-provision-success":"Pomyślne przygotowanie urządzenia", + "type-provision-failure":"Niepowodzenie przygotowania urządzenia", + "type-timeseries-updated":"Zaktualizowano telemetrię", + "type-timeseries-deleted":"Usunięto telemetrię", + "type-sms-sent":"Wysłano SMS" + }, + "confirm-on-exit":{ + "message":"Masz niezapisane zmiany. Czy na pewno chcesz opuścić tę stronę?", + "html-message":"Masz niezapisane zmiany.
    Czy na pewno chcesz opuścić tę stronę?", + "title":"Niezapisane zmiany" + }, + "contact":{ + "country":"Kraj", + "city":"Miasto", + "state":"Stan / Prowincja", + "postal-code":"Kod pocztowy", + "postal-code-invalid":"Nieprawidłowy format kodu pocztowego.", + "address":"Adres", + "address2":"Adres 2", + "phone":"Telefon", + "email":"Email", + "no-address":"Brak adresu", + "state-max-length":"Długość stanu powinna być mniejsza niż 256", + "phone-max-length":"Numer telefonu powinien być krótszy niż 256", + "city-max-length":"Nazwa miasta powinna być krótsza niż 256" + }, + "common":{ + "username":"Nazwa użytkownika", + "password":"Hasło", + "enter-username":"Wprowadź nazwę użytkownika", + "enter-password":"Wprowadź hasło", + "enter-search":"Wprowadź wyszukiwanie", + "created-time":"Czas utworzenia", + "loading":"Ładowanie...", + "proceed":"Kontynuuj", + "open-details-page":"Otwórz stronę szczegółów", + "not-found":"Nie znaleziono", + "documentation":"Dokumentacja" + }, + "content-type":{ + "json":"Json", + "text":"Tekst", + "binary":"Binarny (Base64)" + }, + "customer":{ + "customer":"Klient", + "customers":"Klienci", + "management":"Zarządzanie klientami", + "dashboard":"Pulpit nawigacyjny klienta", + "dashboards":"Pulpity nawigacyjne klienta", + "devices":"Urządzenia klienta", + "entity-views":"Widoki encji klienta", + "assets":"Aktywa klienta", + "public-dashboards":"Publiczne pulpity nawigacyjne", + "public-devices":"Publiczne urządzenia", + "public-assets":"Publiczne aktywa", + "public-edges":"Publiczne krawędzie", + "public-entity-views":"Publiczne widoki encji", + "add":"Dodaj klienta", + "delete":"Usuń klienta", + "manage-customer-users":"Zarządzaj użytkownikami klienta", + "manage-customer-devices":"Zarządzaj urządzeniami klienta", + "manage-customer-dashboards":"Zarządzaj pulpitami nawigacyjnymi klienta", + "manage-public-devices":"Zarządzaj publicznymi urządzeniami", + "manage-public-dashboards":"Zarządzaj publicznymi pulpitami nawigacyjnymi", + "manage-customer-assets":"Zarządzaj aktywami klienta", + "manage-public-assets":"Zarządzaj publicznymi aktywami", + "manage-customer-edges":"Zarządzaj krawędziami klienta", + "manage-public-edges":"Zarządzaj publicznymi krawędziami", + "add-customer-text":"Dodaj nowego klienta", + "no-customers-text":"Nie znaleziono klientów", + "customer-details":"Szczegóły klienta", + "delete-customer-title":"Czy na pewno chcesz usunąć klienta '{{customerTitle}}'?", + "delete-customer-text":"Uważaj, po potwierdzeniu klient i wszystkie powiązane dane staną się nieodwracalne.", + "delete-customers-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 klienta} other {# klientów} }?", + "delete-customers-action-title":"Usuń { count, plural, =1 {1 klienta} other {# klientów} }", + "delete-customers-text":"Uważaj, po potwierdzeniu wszyscy wybrani klienci oraz wszystkie powiązane dane staną się nieodwracalne.", + "manage-users":"Zarządzaj użytkownikami", + "manage-assets":"Zarządzaj aktywami", + "manage-devices":"Zarządzaj urządzeniami", + "manage-dashboards":"Zarządzaj pulpitami nawigacyjnymi", + "title":"Tytuł", + "title-required":"Tytuł jest wymagany.", + "title-max-length":"Tytuł powinien być krótszy niż 256", + "description":"Opis", + "details":"Szczegóły", + "events":"Wydarzenia", + "copyId":"Kopiuj ID klienta", + "idCopiedMessage":"ID klienta zostało skopiowane do schowka", + "select-customer":"Wybierz klienta", + "no-customers-matching":"Nie znaleziono klientów pasujących do '{{entity}}'.", + "customer-required":"Klient jest wymagany", + "select-default-customer":"Wybierz domyślnego klienta", + "default-customer":"Domyślny klient", + "default-customer-required":"Domyślny klient jest wymagany do debugowania pulpitu nawigacyjnego na poziomie dzierżawcy", + "search":"Szukaj klientów", + "selected-customers":"{ count, plural, =1 {1 klient} other {# klientów} } wybrano", + "edges":"Instancje krawędzi klienta", + "manage-edges":"Zarządzaj krawędziami" + }, + "date":{ + "last-update-n-ago":"Ostatnia aktualizacja N temu", + "last-update-n-ago-text":"Ostatnia aktualizacja {{ agoText }}", + "custom-date":"Niestandardowa data", + "format":"Format", + "preview":"Podgląd" + }, + "datetime":{ + "date-from":"Data od", + "time-from":"Czas od", + "date-to":"Data do", + "time-to":"Czas do" + }, + "dashboard":{ + "dashboard":"Panel", + "dashboards":"Panele", + "management":"Zarządzanie panelami", + "view-dashboards":"Przeglądaj panele", + "add":"Dodaj panel", + "assign-dashboard-to-customer":"Przypisz panel(y) do klienta", + "assign-dashboard-to-customer-text":"Wybierz panele do przypisania do klienta", + "assign-dashboard-to-edge-title":"Przypisz panel(y) do urządzenia", + "assign-to-customer-text":"Wybierz klienta, do którego przypisane będą panele", + "assign-to-customer":"Przypisz klientowi", + "unassign-from-customer":"Odłącz od klienta", + "make-public":"Udostępnij panel publicznie", + "make-private":"Ustaw panel jako prywatny", + "manage-assigned-customers":"Zarządzaj przypisanymi klientami", + "assigned-customers":"Przypisani klienci", + "assign-to-customers":"Przypisz panele klientom", + "assign-to-customers-text":"Wybierz klientów, do których przypisane będą panele", + "unassign-from-customers":"Odłącz panele od klientów", + "unassign-from-customers-text":"Wybierz klientów, od których odłączone zostaną panele", + "no-dashboards-text":"Nie znaleziono paneli", + "no-widgets":"Brak skonfigurowanych widżetów", + "add-widget":"Dodaj nowy widżet", + "add-widget-button-text":"Dodaj widżet", + "title":"Tytuł", + "image":"Obraz panelu", + "mobile-app-settings":"Ustawienia aplikacji mobilnej", + "mobile-order":"Kolejność panelu w aplikacji mobilnej", + "mobile-hide":"Ukryj panel w aplikacji mobilnej", + "update-image":"Aktualizuj obraz panelu", + "take-screenshot":"Zrób zrzut ekranu", + "select-widget-title":"Wybierz widżet", + "select-widget-value":"{{title}}: wybierz widżet", + "select-widget-subtitle":"Lista dostępnych typów widżetów", + "delete":"Usuń panel", + "title-required":"Tytuł jest wymagany.", + "title-max-length":"Tytuł powinien mieć mniej niż 256 znaków", + "description":"Opis", + "details":"Szczegóły", + "dashboard-details":"Szczegóły panelu", + "add-dashboard-text":"Dodaj nowy panel", + "assign-dashboards":"Przypisz panele", + "assign-new-dashboard":"Przypisz nowy panel", + "assign-dashboards-text":"Przypisz { count, plural, =1 {1 panel} other {# panele} } do klientów", + "unassign-dashboards-action-text":"Odłącz { count, plural, =1 {1 panel} other {# panele} } od klientów", + "delete-dashboards":"Usuń panele", + "unassign-dashboards":"Odłącz panele", + "unassign-dashboards-action-title":"Odłącz { count, plural, =1 {1 panel} other {# panele} } od klienta", + "delete-dashboard-title":"Czy na pewno chcesz usunąć panel '{{dashboardTitle}}'?", + "delete-dashboard-text":"Bądź ostrożny, po potwierdzeniu panel i wszystkie związane z nim dane staną się nieodwracalnie utracone.", + "delete-dashboards-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 panel} other {# panele} }?", + "delete-dashboards-action-title":"Usuń { count, plural, =1 {1 panel} other {# panele} }", + "delete-dashboards-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane panele zostaną usunięte, a wszystkie związane z nimi dane staną się nieodwracalnie utracone.", + "unassign-dashboard-title":"Czy na pewno chcesz odłączyć panel '{{dashboardTitle}}'?", + "unassign-dashboard-text":"Po potwierdzeniu panel zostanie odłączony i nie będzie dostępny dla klienta.", + "unassign-dashboard":"Odłącz panel", + "unassign-dashboards-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 panel} other {# panele} }?", + "unassign-dashboards-text":"Po potwierdzeniu wszystkie wybrane panele zostaną odłączone i nie będą dostępne dla klienta.", + "public-dashboard-title":"Panel jest teraz publiczny", + "public-dashboard-text":"Twój panel {{dashboardTitle}} jest teraz publiczny i dostępny za pomocą następującego publicznego linku:", + "public-dashboard-notice":"Uwaga: Nie zapomnij udostępnić powiązanych urządzeń publicznie, aby uzyskać do nich dostęp.", + "make-private-dashboard-title":"Czy na pewno chcesz uczynić panel '{{dashboardTitle}}' prywatnym?", + "make-private-dashboard-text":"Po potwierdzeniu panel zostanie uczyniony prywatnym i nie będzie dostępny dla innych.", + "make-private-dashboard":"Ustaw panel jako prywatny", + "socialshare-text":"'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title":"'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard":"Wybierz panel", + "no-dashboards-matching":"Nie znaleziono paneli pasujących do '{{entity}}'", + "dashboard-required":"Panel jest wymagany.", + "select-existing":"Wybierz istniejący panel", + "create-new":"Utwórz nowy panel", + "new-dashboard-title":"Tytuł nowego panelu", + "open-dashboard":"Otwórz panel", + "set-background":"Ustaw tło", + "background-color":"Kolor tła", + "background-image":"Obraz tła", + "background-size-mode":"Tryb rozmiaru tła", + "no-image":"Brak wybranego obrazu", + "empty-image":"Brak obrazu", + "drop-image":"Upuść obraz lub kliknij, aby wybrać plik do przesłania.", + "maximum-upload-file-size":"Maksymalny rozmiar pliku do przesłania: {{ size }}", + "cannot-upload-file":"Nie można przesłać pliku", + "settings":"Ustawienia", + "layout-settings":"Ustawienia układu", + "columns-count":"Liczba kolumn", + "columns-count-required":"Liczba kolumn jest wymagana.", + "min-columns-count-message":"Dozwolona jest tylko minimalna liczba kolumn wynosząca 10.", + "max-columns-count-message":"Dozwolona jest tylko maksymalna liczba kolumn wynosząca 1000.", + "widgets-margins":"Margines między widżetami", + "margin-required":"Wartość marginesu jest wymagana.", + "min-margin-message":"Dozwolona jest tylko wartość minimalna marginesu wynosząca 0.", + "max-margin-message":"Dozwolona jest tylko wartość maksymalna marginesu wynosząca 50.", + "horizontal-margin":"Margines poziomy", + "horizontal-margin-required":"Wartość marginesu poziomego jest wymagana.", + "min-horizontal-margin-message":"Dozwolona jest tylko wartość minimalna marginesu poziomego wynosząca 0.", + "max-horizontal-margin-message":"Dozwolona jest tylko wartość maksymalna marginesu poziomego wynosząca 50.", + "vertical-margin":"Margines pionowy", + "vertical-margin-required":"Wartość marginesu pionowego jest wymagana.", + "min-vertical-margin-message":"Dozwolona jest tylko wartość minimalna marginesu pionowego wynosząca 0.", + "max-vertical-margin-message":"Dozwolona jest tylko wartość maksymalna marginesu pionowego wynosząca 50.", + "apply-outer-margin":"Zastosuj margines do boków układu", + "autofill-height":"Automatycznie wypełnij wysokość układu", + "mobile-layout":"Ustawienia układu na urządzenia mobilne", + "mobile-row-height":"Wysokość rzędu na urządzenia mobilne, px", + "mobile-row-height-required":"Wartość wysokości rzędu na urządzenia mobilne jest wymagana.", + "min-mobile-row-height-message":"Dozwolona jest tylko minimalna wartość wysokości rzędu na urządzenia mobilne wynosząca 5 pikseli.", + "max-mobile-row-height-message":"Dozwolona jest tylko maksymalna wartość wysokości rzędu na urządzenia mobilne wynosząca 200 pikseli.", + "title-settings":"Ustawienia tytułu", + "display-title":"Wyświetl tytuł panelu", + "title-color":"Kolor tytułu", + "toolbar-settings":"Ustawienia paska narzędziowego", + "hide-toolbar":"Ukryj pasek narzędziowy", + "toolbar-always-open":"Zawsze otwarty pasek narzędziowy", + "display-dashboards-selection":"Wyświetl wybór paneli", + "display-entities-selection":"Wyświetl wybór jednostek", + "display-filters":"Wyświetl filtry", + "display-dashboard-timewindow":"Wyświetl okno czasowe panelu", + "display-dashboard-export":"Wyświetl eksport panelu", + "display-update-dashboard-image":"Wyświetl aktualizację obrazu panelu", + "dashboard-logo-settings":"Ustawienia logo panelu", + "display-dashboard-logo":"Wyświetl logo w trybie pełnoekranowym panelu", + "dashboard-logo-image":"Obraz logo panelu", + "advanced-settings":"Zaawansowane ustawienia", + "dashboard-css":"CSS panelu", + "import":"Importuj panel", + "export":"Eksportuj panel", + "export-failed-error":"Nie można wyeksportować panelu: {{error}}", + "create-new-dashboard":"Utwórz nowy panel", + "dashboard-file":"Plik panelu", + "invalid-dashboard-file-error":"Nie można zaimportować panelu: Nieprawidłowa struktura danych panelu.", + "dashboard-import-missing-aliases-title":"Skonfiguruj aliasy używane przez importowany panel", + "create-new-widget":"Utwórz nowy widżet", + "import-widget":"Importuj widżet", + "widget-file":"Plik widżetu", + "invalid-widget-file-error":"Nie można zaimportować widżetu: Nieprawidłowa struktura danych widżetu.", + "widget-import-missing-aliases-title":"Skonfiguruj aliasy używane przez importowany widżet", + "open-toolbar":"Otwórz pasek narzędziowy panelu", + "close-toolbar":"Zamknij pasek narzędziowy panelu", + "configuration-error":"Błąd konfiguracji", + "alias-resolution-error-title":"Błąd konfiguracji aliasów panelu", + "invalid-aliases-config":"Nie można znaleźć żadnych urządzeń pasujących do niektórych filtrów aliasów.
    Skontaktuj się z administratorem w celu rozwiązania tego problemu.", + "select-devices":"Wybierz urządzenia", + "assignedToCustomer":"Przypisane do klienta", + "assignedToCustomers":"Przypisane do klientów", + "public":"Publiczne", + "copyId":"Skopiuj identyfikator panelu", + "idCopiedMessage":"Identyfikator panelu został skopiowany do schowka", + "public-link":"Publiczny link", + "copy-public-link":"Skopiuj publiczny link", + "public-link-copied-message":"Publiczny link do panelu został skopiowany do schowka", + "manage-states":"Zarządzaj stanami panelu", + "states":"Stany panelu", + "states-short":"Stany", + "search-states":"Szukaj stanów panelu", + "selected-states":"{ count, plural, =1 {1 stan panelu} other {# stany panelu} } wybranych", + "edit-state":"Edytuj stan panelu", + "delete-state":"Usuń stan panelu", + "add-state":"Dodaj stan panelu", + "no-states-text":"Nie znaleziono stanów", + "state":"Stan panelu", + "state-name":"Nazwa", + "state-name-required":"Wymagana jest nazwa stanu panelu.", + "state-id":"Identyfikator stanu", + "state-id-required":"Wymagane jest Identyfikator stanu panelu.", + "state-id-exists":"Stan panelu o tym samym identyfikatorze już istnieje.", + "is-root-state":"Główny stan", + "delete-state-title":"Usuń stan panelu", + "delete-state-text":"Czy na pewno chcesz usunąć stan panelu o nazwie '{{stateName}}'?", + "show-details":"Pokaż szczegóły", + "hide-details":"Ukryj szczegóły", + "select-state":"Wybierz docelowy stan", + "state-controller":"Kontroler stanu", + "state-controller-default":"statyczny (przestarzały)", + "search":"Szukaj paneli", + "selected-dashboards":"{ count, plural, =1 {1 panel} other {# panele} } wybranych", + "home-dashboard":"Panel główny", + "home-dashboard-hide-toolbar":"Ukryj pasek narzędzi panelu głównego", + "unassign-dashboard-from-edge-text":"Po potwierdzeniu panel zostanie odłączony i nie będzie dostępny dla krawędzi.", + "unassign-dashboards-from-edge-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 panel} other {# panele} }?", + "unassign-dashboards-from-edge-text":"Po potwierdzeniu wszystkie wybrane panele zostaną odłączone i nie będą dostępne dla krawędzi.", + "assign-dashboard-to-edge":"Przypisz panele do krawędzi", + "assign-dashboard-to-edge-text":"Proszę wybrać panele do przypisania do krawędzi", + "non-existent-dashboard-state-error":"Stan panelu o identyfikatorze \"{{ stateId }}\" nie istnieje", + "edit-mode":"Tryb edycji", + "duplicate-state-action":"Zduplikuj stan" + }, + "datakey":{ + "settings":"Ustawienia", + "general":"Ogólne", + "advanced":"Zaawansowane", + "key":"Klucz", + "label":"Etykieta", + "color":"Kolor", + "units":"Symbol specjalny do wyświetlenia obok wartości", + "decimals":"Liczba cyfr po przecinku", + "data-generation-func":"Funkcja generacji danych", + "use-data-post-processing-func":"Użyj funkcji przetwarzania danych po generacji", + "configuration":"Konfiguracja klucza danych", + "timeseries":"Szereg czasowy", + "attributes":"Atrybuty", + "entity-field":"Pole encji", + "alarm":"Pola alarmowe", + "timeseries-required":"Wymagane są serie czasowe encji.", + "timeseries-or-attributes-required":"Wymagane są serie czasowe/cechy encji.", + "alarm-fields-timeseries-or-attributes-required":"Wymagane są pola alarmowe lub serie czasowe/cechy encji.", + "maximum-timeseries-or-attributes":"Maksymalnie { count, plural, =1 {1 seria czasowa/cecha jest dozwolona.} other {# serie czasowe/cechy są dozwolone} }", + "alarm-fields-required":"Wymagane są pola alarmowe.", + "function-types":"Typy funkcji", + "function-type":"Typ funkcji", + "function-types-required":"Wymagane są typy funkcji.", + "data-keys":"Klucze danych", + "data-key":"Klucz danych", + "data-keys-required":"Wymagane są klucze danych.", + "data-key-required":"Wymagany jest klucz danych.", + "alarm-keys":"Klucze danych alarmowych", + "alarm-key":"Klucz danych alarmowych", + "alarm-key-functions":"Funkcje klucza alarmowego", + "alarm-key-function":"Funkcja klucza alarmowego", + "latest-keys":"Ostatnie klucze danych", + "latest-key":"Ostatni klucz danych", + "latest-key-functions":"Funkcje ostatniego klucza", + "latest-key-function":"Funkcja ostatniego klucza", + "timeseries-keys":"Klucze danych szeregów czasowych", + "timeseries-key":"Klucz danych szeregów czasowych", + "timeseries-key-functions":"Funkcje klucza szeregów czasowych", + "timeseries-key-function":"Funkcja klucza szeregów czasowych", + "maximum-function-types":"Maksymalnie { count, plural, =1 {1 typ funkcji jest dozwolony.} other {# typy funkcji są dozwolone} }", + "time-description":"znacznik czasu bieżącej wartości;", + "value-description":"bieżąca wartość;", + "prev-value-description":"wynik poprzedniego wywołania funkcji;", + "time-prev-description":"znacznik czasu poprzedniej wartości;", + "prev-orig-value-description":"oryginalna poprzednia wartość;", + "aggregation":"Agregacja", + "aggregation-type-hint-common":"Ze względów wydajnościowych obliczenia wartości agregowanej są dostępne tylko dla stałych interwałów czasowych, takich jak \"bieżący dzień\", \"bieżący miesiąc\", itp., i nie są dostępne dla interwałów przesuwnych, takich jak 'ostatnie 30 minut' lub 'ostatnie 24 godziny'.", + "aggregation-type-none-hint":"Weź ostatnią wartość.", + "aggregation-type-min-hint":"Znajdź minimalną wartość między punktami danych w wybranym oknie czasowym.", + "aggregation-type-max-hint":"Znajdź maksymalną wartość między punktami danych w wybranym oknie czasowym.", + "aggregation-type-avg-hint":"Oblicz średnią wartość między punktami danych w wybranym oknie czasowym.", + "aggregation-type-sum-hint":"Dodaj wszystkie wartości punktów danych w wybranym oknie czasowym.", + "aggregation-type-count-hint":"Całkowita liczba punktów danych w wybranym oknie czasowym.", + "delta-calculation":"Obliczenia delta", + "enable-delta-calculation":"Włącz obliczenia delta", + "enable-delta-calculation-hint":"Po włączeniu, wartość klucza danych jest obliczana na podstawie wartości agregowanych w wybranym oknie czasowym i okresie porównawczym. Ze względów wydajnościowych obliczenia delta są dostępne tylko dla okien czasowych historycznych, a nie dla wartości czasu rzeczywistego. Na przykład możesz obliczyć różnicę między zużyciem energii wczoraj a zużyciem energii przedwczoraj.", + "delta-calculation-result":"Wynik obliczeń delta", + "delta-calculation-result-previous-value":"Poprzednia wartość", + "delta-calculation-result-delta-absolute":"Delta (bezwzględna)", + "delta-calculation-result-delta-percent":"Delta (procentowa)", + "source":"Źródło", + "latest":"Najnowsze", + "latest-value":"Najnowsza wartość", + "delta":"delta", + "percent":"procent", + "absolute":"bezwzględna" + }, + "datasource":{ + "type":"Typ źródła danych", + "name":"Nazwa", + "label":"Etykieta", + "add-datasource-prompt":"Proszę dodać źródło danych" + }, + "details":{ + "details":"Szczegóły", + "edit-mode":"Tryb edycji", + "edit-json":"Edytuj JSON", + "toggle-edit-mode":"Przełącz tryb edycji" + }, + "device":{ + "device":"Urządzenie", + "device-required":"Urządzenie jest wymagane.", + "devices":"Urządzenia", + "management":"Zarządzanie urządzeniem", + "view-devices":"Zobacz urządzenia", + "device-alias":"Alias urządzenia", + "device-type-max-length":"Typ urządzenia powinien mieć mniej niż 256 znaków", + "aliases":"Aliasy urządzeń", + "no-alias-matching":"'{{alias}}' nie znaleziono.", + "no-aliases-found":"Nie znaleziono aliasów.", + "no-key-matching":"'{{key}}' nie znaleziono.", + "no-keys-found":"Nie znaleziono kluczy.", + "create-new-alias":"Utwórz nowy!", + "create-new-key":"Utwórz nowy!", + "duplicate-alias-error":"Znaleziono duplikat aliasu '{{alias}}'.
    Aliasy urządzeń muszą być unikalne w ramach panelu.", + "configure-alias":"Konfiguruj alias '{{alias}}'", + "no-devices-matching":"Nie znaleziono urządzeń pasujących do '{{entity}}'.", + "alias":"Alias", + "alias-required":"Alias urządzenia jest wymagany.", + "remove-alias":"Usuń alias urządzenia", + "add-alias":"Dodaj alias urządzenia", + "name-starts-with":"Wyrażenie nazwy urządzenia", + "help-text":"Użyj '%' według potrzeb: '%device_name_contains%', '%device_name_ends%', 'device_starts_with'.", + "device-list":"Lista urządzeń", + "use-device-name-filter":"Użyj filtra", + "device-list-empty":"Brak wybranych urządzeń.", + "device-name-filter-required":"Filtr nazwy urządzenia jest wymagany.", + "device-name-filter-no-device-matched":"Nie znaleziono urządzeń rozpoczynających się od '{{device}}'.", + "add":"Dodaj urządzenie", + "assign-to-customer":"Przypisz do klienta", + "assign-device-to-customer":"Przypisz urządzenia do klienta", + "assign-device-to-customer-text":"Proszę wybrać urządzenia do przypisania do klienta", + "assign-device-to-edge-title":"Przypisz urządzenia do krawędzi", + "assign-device-to-edge-text":"Proszę wybrać urządzenia do przypisania do krawędzi", + "make-public":"Udostępnij publicznie", + "make-private":"Udostępnij prywatnie", + "no-devices-text":"Nie znaleziono urządzeń", + "assign-to-customer-text":"Proszę wybrać klienta, do którego przypisane będą urządzenia", + "device-details":"Szczegóły urządzenia", + "add-device-text":"Dodaj nowe urządzenie", + "credentials":"Poświadczenia", + "manage-credentials":"Zarządzaj poświadczeniami", + "delete":"Usuń urządzenie", + "assign-devices":"Przypisz urządzenia", + "assign-devices-text":"Przypisz { count, plural, =1 {1 urządzenie} other {# urządzeń} } do klienta", + "delete-devices":"Usuń urządzenia", + "unassign-from-customer":"Odłącz od klienta", + "unassign-devices":"Odłącz urządzenia", + "unassign-devices-action-title":"Odłącz { count, plural, =1 {1 urządzenie} other {# urządzeń} } od klienta", + "unassign-device-from-edge-title":"Czy na pewno chcesz odłączyć urządzenie '{{deviceName}}'?", + "unassign-device-from-edge-text":"Po potwierdzeniu urządzenie zostanie odłączone i nie będzie dostępne przez krawędź.", + "unassign-devices-from-edge":"Odłącz urządzenia od krawędzi", + "assign-new-device":"Przypisz nowe urządzenie", + "make-public-device-title":"Czy na pewno chcesz udostępnić publicznie urządzenie '{{deviceName}}'?", + "make-public-device-text":"Po potwierdzeniu urządzenie i wszystkie jego dane będą dostępne publicznie dla innych.", + "make-private-device-title":"Czy na pewno chcesz udostępnić prywatnie urządzenie '{{deviceName}}'?", + "make-private-device-text":"Po potwierdzeniu urządzenie i wszystkie jego dane będą dostępne prywatnie i nie będą dostępne dla innych.", + "view-credentials":"Zobacz poświadczenia", + "delete-device-title":"Czy na pewno chcesz usunąć urządzenie '{{deviceName}}'?", + "delete-device-text":"Bądź ostrożny, po potwierdzeniu urządzenie i wszystkie powiązane dane staną się nieodwracalnie utracone.", + "delete-devices-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 urządzenie} other {# urządzeń} }?", + "delete-devices-action-title":"Usuń { count, plural, =1 {1 urządzenie} other {# urządzeń} }", + "delete-devices-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane urządzenia zostaną usunięte, a wszystkie powiązane dane staną się nieodwracalnie utracone.", + "unassign-device-title":"Czy na pewno chcesz odłączyć urządzenie '{{deviceName}}'?", + "unassign-device-text":"Po potwierdzeniu urządzenie zostanie odłączone i nie będzie dostępne przez klienta.", + "unassign-device":"Odłącz urządzenie", + "unassign-devices-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 urządzenie} other {# urządzeń} }?", + "unassign-devices-text":"Po potwierdzeniu wszystkie wybrane urządzenia zostaną odłączone i nie będą dostępne przez klienta.", + "device-credentials":"Poświadczenia urządzenia", + "loading-device-credentials":"Ładowanie poświadczeń urządzenia...", + "credentials-type":"Typ poświadczeń", + "access-token":"Token dostępu", + "access-token-required":"Token dostępu jest wymagany.", + "access-token-invalid":"Długość tokenu dostępu musi wynosić od 1 do 32 znaków.", + "certificate-pem-format":"Certyfikat w formacie PEM", + "certificate-pem-format-required":"Certyfikat jest wymagany.", + "copy-access-token":"Skopiuj token dostępu", + "copy-certificate":"Skopiuj certyfikat", + "copy-client-id":"Skopiuj identyfikator klienta", + "copy-user-name":"Skopiuj nazwę użytkownika", + "copy-password":"Skopiuj hasło", + "generate-client-id":"Generuj identyfikator klienta", + "generate-user-name":"Generuj nazwę użytkownika", + "generate-password":"Generuj hasło", + "generate-access-token":"Generuj token dostępu", + "lwm2m-security-config":{ + "identity":"Identyfikator klienta", + "identity-required":"Identyfikator klienta jest wymagany.", + "identity-tooltip":"Identyfikator PSK to dowolny identyfikator PSK o długości do 128 bajtów, zgodny z opisem w standardzie [RFC7925].\nIdentyfikator PSK MUSI być najpierw przekształcony w ciąg znaków, a następnie zakodowany na bajty przy użyciu UTF-8.", + "client-key":"Klucz klienta", + "client-key-required":"Klucz klienta jest wymagany.", + "client-key-tooltip-prk":"Klucz publiczny RPK lub identyfikator muszą być zgodne ze standardem [RFC7250] i zakodowane w formacie Base64!", + "client-key-tooltip-psk":"Klucz PSK musi być zgodny ze standardem [RFC4279] i formatem HexDec: 32, 64, 128 znaków!", + "endpoint":"Nazwa klienta Endpoint", + "endpoint-required":"Nazwa klienta Endpoint jest wymagana.", + "client-public-key":"Klucz publiczny klienta", + "client-public-key-hint":"Jeśli klucz publiczny klienta jest pusty, używane będzie zaufane certyfikat", + "client-public-key-tooltip":"Klucz publiczny X509 musi być w formacie zakodowanym DER X509v3 i obsługiwać wyłącznie algorytm EC, a następnie zakodowany w formacie Base64!", + "mode":"Tryb konfiguracji zabezpieczeń", + "client-tab":"Konfiguracja zabezpieczeń klienta", + "client-certificate":"Certyfikat klienta", + "bootstrap-tab":"Klient rozruchowy", + "bootstrap-server":"Serwer rozruchowy", + "lwm2m-server":"Serwer LwM2M", + "client-publicKey-or-id":"Klucz publiczny klienta lub Identyfikator", + "client-publicKey-or-id-required":"Klucz publiczny klienta lub Identyfikator są wymagane.", + "client-publicKey-or-id-tooltip-psk":"Identyfikator PSK to dowolny identyfikator PSK o długości do 128 bajtów, zgodny z opisem w standardzie [RFC7925].\nIdentyfikator PSK MUSI być najpierw przekształcony w ciąg znaków, a następnie zakodowany na bajty przy użyciu UTF-8.", + "client-publicKey-or-id-tooltip-rpk":"Klucz publiczny RPK lub identyfikator muszą być zgodne ze standardem [RFC7250] i zakodowane w formacie Base64!", + "client-publicKey-or-id-tooltip-x509":"Klucz publiczny X509 musi być w formacie zakodowanym DER X509v3 i obsługiwać wyłącznie algorytm EC, a następnie zakodowany w formacie Base64!", + "client-secret-key":"Tajny klucz klienta", + "client-secret-key-required":"Tajny klucz klienta jest wymagany.", + "client-secret-key-tooltip-psk":"Klucz PSK musi być zgodny ze standardem [RFC4279] i formatem HexDec: 32, 64, 128 znaków!", + "client-secret-key-tooltip-prk":"Tajny klucz RPK musi być w formacie PKCS_8 (kodowanie DER, standard [RFC5958]) i zakodowany w formacie Base64!", + "client-secret-key-tooltip-x509":"Tajny klucz X509 musi być w formacie PKCS_8 (kodowanie DER, standard [RFC5958]) i zakodowany w formacie Base64!" + }, + "client-id":"ID klienta", + "client-id-pattern":"Zawiera nieprawidłowy znak.", + "user-name":"Nazwa użytkownika", + "user-name-required":"Nazwa użytkownika jest wymagana.", + "client-id-or-user-name-necessary":"ID klienta i/lub Nazwa użytkownika są wymagane", + "password":"Hasło", + "secret":"Sekret", + "secret-required":"Sekret jest wymagany.", + "device-type":"Profil urządzenia", + "device-type-required":"Typ urządzenia jest wymagany.", + "select-device-type":"Wybierz typ urządzenia", + "enter-device-type":"Wprowadź profil urządzenia", + "any-device":"Dowolne urządzenie", + "no-device-types-matching":"Nie znaleziono profili urządzeń pasujących do '{{entitySubtype}}'.", + "device-type-list-empty":"Nie wybrano profili urządzeń!", + "device-profile-type-list-empty":"Należy wybrać co najmniej jeden profil urządzenia.", + "device-types":"Profile urządzeń", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "name-max-length":"Nazwa powinna być krótsza niż 256", + "label-max-length":"Etykieta powinna być krótsza niż 256", + "description":"Opis", + "label":"Etykieta", + "events":"Zdarzenia", + "details":"Szczegóły", + "copyId":"Kopiuj ID urządzenia", + "copyAccessToken":"Kopiuj token dostępu", + "copy-mqtt-authentication":"Kopiuj poświadczenia MQTT", + "idCopiedMessage":"ID urządzenia zostało skopiowane do schowka", + "accessTokenCopiedMessage":"Token dostępu do urządzenia został skopiowany do schowka", + "mqtt-authentication-copied-message":"Poświadczenia MQTT urządzenia zostały skopiowane do schowka", + "assignedToCustomer":"Przypisane do klienta", + "unable-delete-device-alias-title":"Nie można usunąć aliasu urządzenia", + "unable-delete-device-alias-text":"Alias urządzenia '{{deviceAlias}}' nie może być usunięty, ponieważ jest używany przez następujące widżety:
    {{widgetsList}}", + "is-gateway":"Jest bramą", + "overwrite-activity-time":"Nadpisz czas aktywności podłączonego urządzenia", + "device-filter":"Filtr urządzenia", + "device-filter-title":"Filtr urządzenia", + "filter-title":"Filtr", + "device-state":"Stan urządzenia", + "state":"Stan", + "any":"Dowolny", + "active":"Aktywny", + "inactive":"Nieaktywny", + "public":"Publiczny", + "device-public":"Urządzenie jest publiczne", + "select-device":"Wybierz urządzenie", + "import":"Importuj urządzenie", + "device-file":"Plik urządzenia", + "search":"Wyszukaj urządzenia", + "selected-devices":"{ count, plural, =1 {1 urządzenie} other {# urządzeń} } wybranych", + "device-configuration":"Konfiguracja urządzenia", + "transport-configuration":"Konfiguracja transportu", + "wizard":{ + "device-details":"Szczegóły urządzenia" + }, + "unassign-devices-from-edge-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 urządzenie} other {# urządzeń} }?", + "unassign-devices-from-edge-text":"Po potwierdzeniu wszystkie wybrane urządzenia zostaną odłączone i nie będą dostępne dla krawędzi.", + "time":"Czas", + "connectivity":{ + "check-connectivity":"Sprawdź łączność", + "device-created-check-connectivity":"Urządzenie utworzone. Sprawdźmy łączność!", + "loading-check-connectivity-command":"Ładowanie poleceń sprawdzania łączności...", + "use-following-instructions":"Skorzystaj z poniższych instrukcji, aby wysyłać telemetrię w imieniu urządzenia za pomocą konsoli", + "execute-following-command":"Wykonaj poniższe polecenie", + "install-curl-windows":"Począwszy od systemu Windows 10 b17063, cURL jest dostępny domyślnie", + "install-curl-macos":"Począwszy od Mac OS X 10.2 6C115 (Jaguar), cURL jest dostępny domyślnie", + "install-mqtt-windows":"Użyj instrukcji do pobrania, zainstalowania, skonfigurowania i uruchomienia mosquitto_pub", + "install-coap-client":"Użyj instrukcji do pobrania, zainstalowania, skonfigurowania i uruchomienia klienta coap", + "install-necessary-client-tools":"Zainstaluj niezbędne narzędzia klienta", + "mqtts-x509-command":"Skorzystaj z poniższej dokumentacji, aby połączyć urządzenie za pomocą MQTT z uwierzytelnianiem X509", + "coaps-x509-command":"Skorzystaj z poniższej dokumentacji, aby połączyć urządzenie za pomocą CoAP przez DTLS z uwierzytelnianiem X509", + "snmp-command":"Skorzystaj z poniższej dokumentacji, aby połączyć urządzenie za pomocą SNMP.", + "sparkplug-command":"Skorzystaj z poniższej dokumentacji, aby połączyć urządzenie za pomocą MQTT Sparkplug.", + "lwm2m-command":"Skorzystaj z poniższej dokumentacji, aby połączyć urządzenie za pomocą LWM2M." + } + }, + "asset-profile":{ + "asset-profile":"Profil zasobu", + "asset-profiles":"Profile zasobów", + "all-asset-profiles":"Wszystkie", + "add":"Dodaj profil zasobu", + "edit":"Edytuj profil zasobu", + "asset-profile-details":"Szczegóły profilu zasobu", + "no-asset-profiles-text":"Nie znaleziono profili zasobów", + "search":"Szukaj profili zasobów", + "selected-asset-profiles":"{ count, plural, =1 {1 profil zasobu} other {# profile zasobów} } wybrano", + "no-asset-profiles-matching":"Nie znaleziono profilu zasobu pasującego do '{{entity}}'.", + "asset-profile-required":"Wymagany profil zasobu", + "idCopiedMessage":"Identyfikator profilu zasobu został skopiowany do schowka", + "set-default":"Ustaw jako domyślny profil zasobu", + "delete":"Usuń profil zasobu", + "copyId":"Kopiuj identyfikator profilu zasobu", + "name-max-length":"Nazwa powinna mieć mniej niż 256 znaków", + "new-device-profile-name":"Nazwa profilu zasobu", + "new-device-profile-name-required":"Wymagana jest nazwa profilu zasobu.", + "name":"Nazwa", + "name-required":"Wymagana jest nazwa.", + "image":"Zdjęcie profilu zasobu", + "description":"Opis", + "default":"Domyślny", + "default-rule-chain":"Domyślny łańcuch reguł", + "default-edge-rule-chain":"Domyślny łańcuch reguł na krawędzi", + "default-edge-rule-chain-hint":"Używane na krawędzi jako łańcuch reguł do przetwarzania przychodzących danych dla zasobów tego profilu zasobu", + "mobile-dashboard":"Mobilna konsola", + "mobile-dashboard-hint":"Używane przez aplikację mobilną jako konsola szczegółów zasobu", + "select-queue-hint":"Wybierz z listy rozwijanej.", + "delete-asset-profile-title":"Czy na pewno chcesz usunąć profil zasobu '{{assetProfileName}}'?", + "delete-asset-profile-text":"Bądź ostrożny, po potwierdzeniu profil zasobu i wszystkie związane dane staną się nieodwracalnie utracone.", + "delete-asset-profiles-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 profil zasobu} other {# profile zasobów} }?", + "delete-asset-profiles-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane profile zasobów zostaną usunięte, a wszystkie związane dane staną się nieodwracalnie utracone.", + "set-default-asset-profile-title":"Czy na pewno chcesz ustawić profil zasobu '{{assetProfileName}}' jako domyślny?", + "set-default-asset-profile-text":"Po potwierdzeniu profil zasobu zostanie oznaczony jako domyślny i będzie używany dla nowych zasobów bez określonego profilu.", + "no-asset-profiles-found":"Nie znaleziono profili zasobów.", + "create-new-asset-profile":"Utwórz nowy!", + "create-asset-profile":"Utwórz nowy profil zasobu", + "import":"Importuj profil zasobu", + "export":"Eksportuj profil zasobu", + "export-failed-error":"Nie można wyeksportować profilu zasobu: {{error}}", + "asset-profile-file":"Plik profilu zasobu", + "invalid-asset-profile-file-error":"Nie można zaimportować profilu zasobu: Nieprawidłowa struktura danych profilu zasobu." + }, + "device-profile":{ + "device-profile":"Profil urządzenia", + "device-profiles":"Profile urządzeń", + "all-device-profiles":"Wszystkie", + "add":"Dodaj profil urządzenia", + "edit":"Edytuj profil urządzenia", + "device-profile-details":"Szczegóły profilu urządzenia", + "no-device-profiles-text":"Nie znaleziono profili urządzeń", + "search":"Szukaj profili urządzeń", + "selected-device-profiles":"{ count, plural, =1 {1 profil urządzenia} other {# profile urządzeń} } wybrano", + "no-device-profiles-matching":"Nie znaleziono profilu urządzenia pasującego do '{{entity}}'.", + "device-profile-required":"Wymagany profil urządzenia", + "idCopiedMessage":"Identyfikator profilu urządzenia został skopiowany do schowka", + "set-default":"Ustaw jako domyślny profil urządzenia", + "delete":"Usuń profil urządzenia", + "copyId":"Kopiuj identyfikator profilu urządzenia", + "name-max-length":"Nazwa powinna mieć mniej niż 256 znaków", + "name":"Nazwa", + "name-required":"Wymagana jest nazwa.", + "type":"Typ profilu", + "type-required":"Wymagany jest typ profilu.", + "type-default":"Domyślny", + "image":"Zdjęcie profilu urządzenia", + "transport-type":"Typ transportu", + "transport-type-required":"Wymagany jest typ transportu.", + "transport-type-default":"Domyślny", + "transport-type-default-hint":"Obsługuje podstawowe transporty MQTT, HTTP i CoAP", + "transport-type-mqtt":"MQTT", + "transport-type-mqtt-hint":"Umożliwia zaawansowane ustawienia transportu MQTT", + "transport-type-coap":"CoAP", + "transport-type-coap-hint":"Umożliwia zaawansowane ustawienia transportu CoAP", + "transport-type-lwm2m":"LWM2M", + "transport-type-lwm2m-hint":"Typ transportu LWM2M", + "transport-type-snmp":"SNMP", + "transport-type-snmp-hint":"Określ konfigurację transportu SNMP", + "transport-type-http":"HTTP", + "description":"Opis", + "default":"Domyślny", + "profile-configuration":"Konfiguracja profilu", + "transport-configuration":"Konfiguracja transportu", + "default-rule-chain":"Domyślny łańcuch reguł", + "default-edge-rule-chain":"Domyślny łańcuch reguł na krawędzi", + "default-edge-rule-chain-hint":"Używane na krawędzi jako łańcuch reguł do przetwarzania przychodzących danych dla urządzeń tego profilu urządzenia", + "mobile-dashboard":"Mobilna konsola", + "mobile-dashboard-hint":"Używane przez aplikację mobilną jako konsola szczegółów urządzenia", + "select-queue-hint":"Wybierz z listy rozwijanej.", + "delete-device-profile-title":"Czy na pewno chcesz usunąć profil urządzenia '{{deviceProfileName}}'?", + "delete-device-profile-text":"Bądź ostrożny, po potwierdzeniu profil urządzenia i wszystkie związane dane, w tym powiązane aktualizacje OTA, staną się nieodwracalnie utracone.", + "delete-device-profiles-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 profil urządzenia} other {# profile urządzeń} }?", + "delete-device-profiles-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane profile urządzeń zostaną usunięte, a wszystkie związane dane, w tym powiązane aktualizacje OTA, staną się nieodwracalnie utracone.", + "set-default-device-profile-title":"Czy na pewno chcesz ustawić profil urządzenia '{{deviceProfileName}}' jako domyślny?", + "set-default-device-profile-text":"Po potwierdzeniu profil urządzenia zostanie oznaczony jako domyślny i będzie używany dla nowych urządzeń bez określonego profilu.", + "no-device-profiles-found":"Nie znaleziono profili urządzeń.", + "create-new-device-profile":"Utwórz nowy!", + "mqtt-device-topic-filters":"Filtr tematów urządzenia MQTT", + "mqtt-device-topic-filters-unique":"Filtry tematów urządzenia MQTT muszą być unikalne.", + "mqtt-device-topic-filters-spark-plug":"Węzeł krawędziowy MQTT Sparkplug B (EoN).", + "mqtt-device-topic-filters-spark-plug-hint":"Zezwól na połączenia z węzłami EoN z ładunkiem i formatem tematu Sparkplug B.", + "mqtt-device-topic-filters-spark-plug-attribute-metric-names":"Metryki SparkPlug do przechowywania jako atrybuty.", + "mqtt-device-topic-filters-spark-plug-attribute-metric-names-hint":"Nazwy metryk SparkPlug, które będą przechowywane jako atrybuty urządzenia. Wszystkie inne metryki będą przechowywane jako telemetria urządzenia.", + "mqtt-device-payload-type":"Typ ładunka urządzenia MQTT", + "mqtt-device-payload-type-json":"JSON", + "mqtt-device-payload-type-proto":"Protobuf", + "mqtt-enable-compatibility-with-json-payload-format":"Włącz zgodność z innymi formatami ładunków.", + "mqtt-enable-compatibility-with-json-payload-format-hint":"Gdy jest włączona, platforma będzie domyślnie używać formatu ładunku Protobuf. Jeśli parsowanie się nie powiedzie, platforma spróbuje użyć formatu ładunku JSON. Przydatne dla wstecznej kompatybilności podczas aktualizacji oprogramowania układowego. Na przykład początkowa wersja oprogramowania używa Json, podczas gdy nowa wersja używa Protobuf. Podczas procesu aktualizacji oprogramowania dla floty urządzeń konieczne jest jednoczesne obsługiwanie zarówno Protobuf, jak i JSON. Tryb zgodności wprowadza niewielki spadek wydajności, więc zaleca się wyłączenie tego trybu, gdy wszystkie urządzenia zostaną zaktualizowane.", + "mqtt-use-json-format-for-default-downlink-topics":"Użyj formatu Json dla domyślnych tematów zwrotnych", + "mqtt-use-json-format-for-default-downlink-topics-hint":"Gdy jest włączone, platforma będzie używać formatu ładunku Json do przesyłania atrybutów i RPC za pomocą następujących tematów: v1/devices/me/attributes/response/$request_id, v1/devices/me/attributes, v1/devices/me/rpc/request/$request_id, v1/devices/me/rpc/response/$request_id. Ta ustawienie nie wpływa na subskrypcje atrybutów i rpc wysyłane za pomocą nowych (v2) tematów: v2/a/res/$request_id, v2/a, v2/r/req/$request_id, v2/r/res/$request_id. Gdzie $request_id to identyfikator żądania.", + "mqtt-send-ack-on-validation-exception":"Wyślij PUBACK w przypadku niepowodzenia walidacji komunikatu PUBLISH", + "mqtt-send-ack-on-validation-exception-hint":"Domyślnie platforma zakończy sesję MQTT w przypadku niepowodzenia walidacji komunikatu. Gdy jest włączone, platforma wyśle potwierdzenie publikacji zamiast zakończenia sesji.", + "snmp-add-mapping":"Dodaj mapowanie SNMP", + "snmp-mapping-not-configured":"Nie skonfigurowano mapowania OID na szeregi czasowe/telemetrię", + "snmp-timseries-or-attribute-name":"Nazwa szeregów czasowych/cechy do mapowania", + "snmp-timseries-or-attribute-type":"Typ szeregów czasowych/cechy do mapowania", + "snmp-method-pdu-type-get-request":"GetRequest", + "snmp-method-pdu-type-get-next-request":"GetNextRequest", + "snmp-oid":"OID", + "transport-device-payload-type-json":"JSON", + "transport-device-payload-type-proto":"Protobuf", + "mqtt-payload-type-required":"Wymagany jest typ ładunka.", + "coap-device-type":"Typ urządzenia CoAP", + "coap-device-payload-type":"Typ ładunka urządzenia CoAP", + "coap-device-type-required":"Wymagany jest typ urządzenia CoAP.", + "coap-device-type-default":"Domyślny", + "coap-device-type-efento":"Efento NB-IoT", + "support-level-wildcards":"Obsługiwane są pojedyncze [+] i wielopoziomowe [#] znaki wieloznaczne.", + "telemetry-topic-filter":"Filtr tematu telemetrii", + "telemetry-topic-filter-required":"Wymagany jest filtr tematu telemetrii.", + "attributes-topic-filter":"Filtr tematu publikacji atrybutów", + "attributes-subscribe-topic-filter":"Filtr tematu subskrypcji atrybutów", + "attributes-topic-filter-required":"Wymagany jest filtr tematu publikacji atrybutów.", + "attributes-subscribe-topic-filter-required":"Wymagany jest filtr tematu subskrypcji atrybutów", + "telemetry-proto-schema":"Schemat protokołu telemetrii", + "telemetry-proto-schema-required":"Wymagany jest schemat protokołu telemetrii.", + "attributes-proto-schema":"Schemat protokołu atrybutów", + "attributes-proto-schema-required":"Wymagany jest schemat protokołu atrybutów.", + "rpc-response-proto-schema":"Schemat protokołu odpowiedzi RPC", + "rpc-response-proto-schema-required":"Wymagany jest schemat protokołu odpowiedzi RPC.", + "rpc-response-topic-filter":"Filtr tematu odpowiedzi RPC", + "rpc-response-topic-filter-required":"Wymagany jest filtr tematu odpowiedzi RPC.", + "rpc-request-proto-schema":"Schemat protokołu żądania RPC", + "rpc-request-proto-schema-required":"Wymagany jest schemat protokołu żądania RPC.", + "rpc-request-proto-schema-hint":"Wiadomość żądania RPC powinna zawsze zawierać pola: string method = 1; int32 requestId = 2; oraz params = 3 dowolnego typu danych.", + "not-valid-pattern-topic-filter":"Nieprawidłowy filtr tematu wzorca", + "not-valid-single-character":"Nieprawidłowe użycie znaku wieloznacznego jednego poziomu", + "not-valid-multi-character":"Nieprawidłowe użycie znaku wieloznacznego wielu poziomów", + "single-level-wildcards-hint":"[+] nadaje się do dowolnego poziomu filtra tematu. Na przykład: v1/devices/+/telemetry lub +/devices/+/attributes.", + "multi-level-wildcards-hint":"[#] może zastąpić sam filtr tematu i musi być ostatnim symbolem tematu. Na przykład: # lub v1/devices/me/#.", + "alarm-rules":"Zasady alarmowe", + "alarm-rules-with-count":"Zasady alarmowe ({{count}})", + "no-alarm-rules":"Brak skonfigurowanych zasad alarmowych", + "add-alarm-rule":"Dodaj zasadę alarmową", + "edit-alarm-rule":"Edytuj zasadę alarmową", + "alarm-type":"Typ alarmu", + "alarm-type-required":"Wymagany jest typ alarmu.", + "alarm-type-unique":"Typ alarmu musi być unikalny w ramach zasad alarmowych profilu urządzenia.", + "alarm-type-max-length":"Typ alarmu powinien być krótszy niż 256", + "create-alarm-pattern":"Utwórz alarm {{alarmType}}", + "create-alarm-rules":"Utwórz zasady alarmowe", + "no-create-alarm-rules":"Brak skonfigurowanych warunków utworzenia", + "add-create-alarm-rule-prompt":"Proszę dodać warunek utworzenia alarmu", + "clear-alarm-rule":"Wyczyść zasadę alarmową", + "no-clear-alarm-rule":"Brak skonfigurowanego warunku wyczyszczenia", + "add-create-alarm-rule":"Dodaj warunek utworzenia alarmu", + "add-clear-alarm-rule":"Dodaj warunek wyczyszczenia", + "select-alarm-severity":"Wybierz stopień pilności alarmu", + "alarm-severity-required":"Wymagany jest stopień pilności alarmu.", + "condition-duration":"Czas trwania warunku", + "condition-duration-value":"Wartość czasu trwania", + "condition-duration-time-unit":"Jednostka czasu", + "condition-duration-value-range":"Wartość czasu trwania powinna zawierać się w zakresie od 1 do 2147483647.", + "condition-duration-value-pattern":"Wartość czasu trwania powinna być liczbą całkowitą.", + "condition-duration-value-required":"Wymagana jest wartość czasu trwania.", + "condition-duration-time-unit-required":"Wymagana jest jednostka czasu.", + "advanced-settings":"Zaawansowane ustawienia", + "alarm-rule-additional-info":"Dodatkowe informacje", + "edit-alarm-rule-additional-info":"Edytuj dodatkowe informacje", + "alarm-rule-additional-info-placeholder":"Proszę podać swoje uwagi i dostosowania tutaj, aby wyświetlić je w szczegółach alarmu w sekcji Dodatkowe informacje", + "alarm-rule-additional-info-hint":"Wskazówka: użyj ${kluczNazwy} do zastąpienia wartości kluczy atrybutu lub telemetrii używanych w warunku zasady alarmowej.", + "alarm-rule-mobile-dashboard":"Panel nawigacyjny na urządzenia przenośne", + "alarm-rule-mobile-dashboard-hint":"Używane przez aplikację mobilną jako panel nawigacyjny szczegółów alarmu", + "alarm-rule-no-mobile-dashboard":"Nie wybrano panelu nawigacyjnego", + "propagate-alarm":"Rozprzestrzeniaj alarm do powiązanych jednostek", + "alarm-rule-relation-types-list":"Typy relacji do rozprzestrzeniania", + "alarm-rule-relation-types-list-hint":"Jeśli nie zostaną wybrane typy relacji do rozprzestrzeniania, alarmy będą rozprzestrzeniane bez filtrowania według typu relacji.", + "propagate-alarm-to-owner":"Rozprzestrzeniaj alarm do właściciela jednostki (Klienta lub Najemcy)", + "propagate-alarm-to-tenant":"Rozprzestrzeniaj alarm do Najemcy", + "alarm-rule-condition":"Warunek zasady alarmowej", + "enter-alarm-rule-condition-prompt":"Proszę dodać warunek zasady alarmowej", + "edit-alarm-rule-condition":"Edytuj warunek zasady alarmowej", + "device-provisioning":"Proces przydzielania urządzenia", + "provision-strategy":"Strategia przydzielania", + "provision-strategy-required":"Wymagana jest strategia przydzielania.", + "provision-strategy-disabled":"Wyłączone", + "provision-strategy-created-new":"Zezwól na tworzenie nowych urządzeń", + "provision-strategy-check-pre-provisioned":"Sprawdź wcześniej przydzielone urządzenia", + "provision-device-key":"Klucz przydzielania urządzenia", + "provision-device-key-required":"Wymagany jest klucz przydzielania urządzenia.", + "copy-provision-key":"Kopiuj klucz przydzielania", + "provision-key-copied-message":"Klucz przydzielania został skopiowany do schowka", + "provision-device-secret":"Sekret przydzielania urządzenia", + "provision-device-secret-required":"Wymagany jest sekret przydzielania urządzenia.", + "copy-provision-secret":"Kopiuj sekret przydzielania", + "provision-secret-copied-message":"Sekret przydzielania został skopiowany do schowka", + "provision-strategy-x509":{ + "certificate-chain":"Łańcuch certyfikatów X509", + "certificate-chain-hint":"Strategia certyfikatów X.509 jest używana do przydzielania urządzeń za pomocą certyfikatów klientów w dwukierunkowej komunikacji TLS.", + "allow-create-new-devices":"Utwórz nowe urządzenia", + "allow-create-new-devices-hint":"Jeśli zaznaczone, nowe urządzenia zostaną utworzone, a certyfikat klienta będzie używany jako dane uwierzytelniające urządzenia.", + "certificate-value":"Certyfikat w formacie PEM", + "certificate-value-required":"Wymagany jest certyfikat w formacie PEM", + "cn-regex-variable":"Zmienna wyrażenia regularnego CN", + "cn-regex-variable-required":"Wymagana jest zmienna wyrażenia regularnego CN", + "cn-regex-variable-hint":"Wymagane do pobrania nazwy urządzenia z wspólnego nazewnictwa certyfikatu X509 urządzenia." + }, + "condition":"Warunek", + "condition-type":"Typ warunku", + "condition-type-simple":"Prosty", + "condition-type-duration":"Czas trwania", + "condition-during":"Podczas {{during}}", + "condition-during-dynamic":"Podczas \"{{attribute}}\" ({{during}})", + "condition-type-repeating":"Powtarzający się", + "condition-type-required":"Wymagany jest typ warunku.", + "condition-repeating-value":"Liczba zdarzeń", + "condition-repeating-value-range":"Liczba zdarzeń powinna mieścić się w zakresie od 1 do 2147483647.", + "condition-repeating-value-pattern":"Liczba zdarzeń powinna być liczbą całkowitą.", + "condition-repeating-value-required":"Wymagana jest liczba zdarzeń.", + "condition-repeat-times":"Powtarza się {count, plural, =1 {1 raz} other {# razy}}", + "condition-repeat-times-dynamic":"Powtarza się \"{attribute}\" ({count, plural, =1 {1 raz} other {# razy}})", + "schedule-type":"Typ harmonogramu", + "schedule-type-required":"Wymagany jest typ harmonogramu.", + "schedule":"Harmonogram", + "edit-schedule":"Edytuj harmonogram alarmu", + "schedule-any-time":"Aktywny przez cały czas", + "schedule-specific-time":"Aktywny o określonej godzinie", + "schedule-custom":"Niestandardowy", + "schedule-day":{ + "monday":"Poniedziałek", + "tuesday":"Wtorek", + "wednesday":"Środa", + "thursday":"Czwartek", + "friday":"Piątek", + "saturday":"Sobota", + "sunday":"Niedziela" + }, + "schedule-days":"Dni", + "schedule-time":"Czas", + "schedule-time-from":"Od", + "schedule-time-to":"Do", + "schedule-days-of-week-required":"Należy wybrać przynajmniej jeden dzień tygodnia.", + "create-device-profile":"Utwórz nowy profil urządzenia", + "import":"Importuj profil urządzenia", + "export":"Eksportuj profil urządzenia", + "export-failed-error":"Nie można wyeksportować profilu urządzenia: {{error}}", + "device-profile-file":"Plik profilu urządzenia", + "invalid-device-profile-file-error":"Nie można zaimportować profilu urządzenia: Nieprawidłowa struktura danych profilu urządzenia.", + "power-saving-mode":"Tryb oszczędzania energii", + "power-saving-mode-type":{ + "default":"Użyj trybu oszczędzania energii profilu urządzenia", + "psm":"Tryb oszczędzania energii (PSM)", + "drx":"Przerwane odbieranie (DRX)", + "edrx":"Rozszerzone przerwane odbieranie (eDRX)" + }, + "edrx-cycle":"Cykl eDRX", + "edrx-cycle-required":"Cykl eDRX jest wymagany.", + "edrx-cycle-pattern":"Cykl eDRX musi być liczbą całkowitą dodatnią.", + "edrx-cycle-min":"Minimalna liczba cykli eDRX to {{ min }} sekundy.", + "paging-transmission-window":"Okno transmisji strony", + "paging-transmission-window-required":"Okno transmisji strony jest wymagane.", + "paging-transmission-window-pattern":"Okno transmisji strony musi być liczbą całkowitą dodatnią.", + "paging-transmission-window-min":"Minimalna liczba okna transmisji strony to {{ min }} sekundy.", + "psm-activity-timer":"Licznik aktywności PSM", + "psm-activity-timer-required":"Licznik aktywności PSM jest wymagany.", + "psm-activity-timer-pattern":"Licznik aktywności PSM musi być liczbą całkowitą dodatnią.", + "psm-activity-timer-min":"Minimalna liczba licznika aktywności PSM to {{ min }} sekundy.", + "lwm2m":{ + "object-list":"Lista obiektów", + "object-list-empty":"Brak wybranych obiektów.", + "no-objects-found":"Nie znaleziono obiektów.", + "no-objects-matching":"Nie znaleziono obiektów pasujących do '{{object}}'.", + "model-tab":"Model LWM2M", + "add-new-instances":"Dodaj nowe instancje", + "instances-list":"Lista instancji", + "instances-list-required":"Lista instancji jest wymagana.", + "instance-id-pattern":"Identyfikator instancji musi być liczbą całkowitą dodatnią.", + "instance-id-max":"Maksymalna wartość identyfikatora instancji to {{max}}.", + "instance":"Instancja", + "resource-label":"#ID Nazwa zasobu", + "observe-label":"Obserwuj", + "attribute-label":"Atrybut", + "telemetry-label":"Telemetria", + "edit-observe-select":"Aby edytować obserwację, wybierz telemetrię lub atrybut", + "edit-attributes-select":"Aby edytować atrybuty, wybierz telemetrię lub atrybut", + "no-attributes-set":"Brak ustawionych atrybutów", + "key-name":"Nazwa klucza", + "key-name-required":"Nazwa klucza jest wymagana", + "attribute-name":"Nazwa atrybutu", + "attribute-name-required":"Nazwa atrybutu jest wymagana.", + "attribute-value":"Wartość atrybutu", + "attribute-value-required":"Wartość atrybutu jest wymagana.", + "attribute-value-pattern":"Wartość atrybutu musi być liczbą całkowitą dodatnią.", + "edit-attributes":"Edytuj atrybuty: {{ name }}", + "view-attributes":"Wyświetl atrybuty: {{ name }}", + "add-attribute":"Dodaj atrybut", + "edit-attribute":"Edytuj atrybut", + "view-attribute":"Wyświetl atrybut", + "remove-attribute":"Usuń atrybut", + "delete-server-text":"Bądź ostrożny, po potwierdzeniu konfiguracja serwera stanie się nieodwracalna.", + "delete-server-title":"Czy na pewno chcesz usunąć serwer?", + "mode":"Tryb konfiguracji zabezpieczeń", + "bootstrap-tab":"Bootstrap", + "bootstrap-server-legend":"Serwer Bootstrap (ShortId...)", + "lwm2m-server-legend":"Serwer LwM2M (ShortId...)", + "server":"Serwer", + "short-id":"Krótki identyfikator serwera", + "short-id-tooltip":"Krótki identyfikator serwera. Używany jako łącze do powiązania instancji obiektu serwera.\nTen identyfikator jednoznacznie identyfikuje każdy serwer LwM2M skonfigurowany dla klienta LwM2M.\nZasób MUSI być ustawiony, gdy Zasób Bootstrap-Server ma wartość „false”.\nWartości ID:0 i ID:65535 NIE MOGĄ być używane do identyfikacji serwera LwM2M.", + "short-id-required":"Krótki identyfikator serwera jest wymagany.", + "short-id-range":"Krótki identyfikator serwera powinien mieć wartość z zakresu od 1 do 65534.", + "short-id-pattern":"Krótki identyfikator serwera musi być liczbą całkowitą dodatnią.", + "lifetime":"Czas życia rejestracji klienta", + "lifetime-required":"Czas życia rejestracji klienta jest wymagany.", + "lifetime-pattern":"Czas życia rejestracji klienta musi być liczbą całkowitą dodatnią.", + "default-min-period":"Minimalny okres między dwoma powiadomieniami (s)", + "default-min-period-tooltip":"Domyślna wartość, jaką klient LwM2M powinien używać dla Minimalnego Okresu Obserwacji w przypadku braku uwzględnienia tego parametru w Obserwacji.", + "default-min-period-required":"Minimalny okres jest wymagany.", + "default-min-period-pattern":"Minimalny okres musi być liczbą całkowitą dodatnią.", + "notification-storing":"Przechowywanie powiadomień przy wyłączonym lub offline", + "binding":"Powiązanie", + "binding-type":{ + "u":"U: Klient jest dostępny za pośrednictwem powiązania UDP w dowolnym czasie.", + "m":"M: Klient jest dostępny za pośrednictwem powiązania MQTT w dowolnym czasie.", + "h":"H: Klient jest dostępny za pośrednictwem powiązania HTTP w dowolnym czasie.", + "t":"T: Klient jest dostępny za pośrednictwem powiązania TCP w dowolnym czasie.", + "s":"S: Klient jest dostępny za pośrednictwem powiązania SMS w dowolnym czasie.", + "n":"N: Klient MUSI wysłać odpowiedź na takie żądanie za pośrednictwem powiązania Non-IP (jest obsługiwane od LWM2M 1.1).", + "uq":"UQ: Połączenie UDP w trybie kolejki (nie jest obsługiwane od LWM2M 1.1)", + "uqs":"UQS: zarówno połączenia UDP, jak i SMS aktywne; UDP w trybie kolejki, SMS w trybie standardowym (nie jest obsługiwane od LWM2M 1.1)", + "tq":"TQ: Połączenie TCP w trybie kolejki (nie jest obsługiwane od LWM2M 1.1)", + "tqs":"TQS: zarówno połączenia TCP, jak i SMS aktywne; TCP w trybie kolejki, SMS w trybie standardowym (nie jest obsługiwane od LWM2M 1.1)", + "sq":"SQ: Połączenie SMS w trybie kolejki (nie jest obsługiwane od LWM2M 1.1)" + }, + "binding-tooltip":"To jest lista w zasobie \"binding\" obiektu serwera LwM2M - /1/x/7. Wskazuje obsługiwane tryby powiązań w kliencie LwM2M. Ta wartość POWINNA być taka sama jak wartość w zasobie \"Supported Binding and Modes\" w obiekcie urządzenia (/3/0/16). Chociaż obsługiwane są różne środki transportu, tylko jedno powiązanie transportowe może być używane podczas całej sesji transportowej. Na przykład, gdy obsługiwane są zarówno UDP, jak i SMS, klient LwM2M i serwer LwM2M mogą wybrać komunikację zarówno przez UDP, jak i SMS przez całą sesję transportową.", + "bootstrap-server":"Serwer rozruchowy", + "lwm2m-server":"Serwer LwM2M", + "include-bootstrap-server":"Załącz aktualizacje serwera rozruchowego", + "bootstrap-update-title":"Masz już skonfigurowany serwer rozruchowy. Czy na pewno chcesz wyłączyć aktualizacje?", + "bootstrap-update-text":"Bądź ostrożny, po potwierdzeniu konfiguracja danych serwera rozruchowego stanie się nieodwracalna.", + "server-host":"Host", + "server-host-required":"Host jest wymagany.", + "server-port":"Port", + "server-port-required":"Port jest wymagany.", + "server-port-pattern":"Port musi być liczbą całkowitą dodatnią.", + "server-port-range":"Port powinien być w zakresie od 1 do 65535.", + "server-public-key":"Klucz publiczny serwera", + "server-public-key-required":"Klucz publiczny serwera jest wymagany.", + "client-hold-off-time":"Czas wstrzymania klienta", + "client-hold-off-time-required":"Czas wstrzymania klienta jest wymagany.", + "client-hold-off-time-pattern":"Czas wstrzymania klienta musi być liczbą całkowitą dodatnią.", + "client-hold-off-time-tooltip":"Czas wstrzymania klienta do użycia tylko z serwerem rozruchowym", + "account-after-timeout":"Konto po przekroczeniu czasu oczekiwania", + "account-after-timeout-required":"Konto po przekroczeniu czasu oczekiwania jest wymagane.", + "account-after-timeout-pattern":"Konto po przekroczeniu czasu oczekiwania musi być liczbą całkowitą dodatnią.", + "account-after-timeout-tooltip":"Wartość konta po przekroczeniu czasu oczekiwania podana przez ten zasób serwera rozruchowego.", + "server-type":"Typ serwera", + "add-new-server-title":"Dodaj nową konfigurację serwera", + "add-server-config":"Dodaj konfigurację serwera", + "add-lwm2m-server-config":"Dodaj serwer LwM2M", + "no-config-servers":"Brak skonfigurowanych serwerów", + "others-tab":"Inne ustawienia", + "client-strategy":"Strategia klienta podczas łączenia", + "client-strategy-label":"Strategia", + "client-strategy-only-observe":"Tylko żądanie obserwacji do klienta po połączeniu początkowym", + "client-strategy-read-all":"Odczytaj wszystkie zasoby i żądanie obserwacji do klienta po rejestracji", + "fw-update":"Aktualizacja oprogramowania", + "fw-update-strategy":"Strategia aktualizacji firmware'u", + "fw-update-strategy-data":"Przesuń aktualizację firmware'u jako plik binarny za pomocą Obiektu 19 i Zasobu 0 (Dane)", + "fw-update-strategy-package":"Przesuń aktualizację firmware'u jako plik binarny za pomocą Obiektu 5 i Zasobu 0 (Pakiet)", + "fw-update-strategy-package-uri":"Automatycznie generuj unikalny adres URL CoAP do pobrania pakietu i przesyłaj aktualizację firmware'u jako Obiekt 5 i Zasób 1 (Pakiet URI)", + "sw-update":"Aktualizacja oprogramowania", + "sw-update-strategy":"Strategia aktualizacji oprogramowania", + "sw-update-strategy-package":"Przesuń plik binarny za pomocą Obiektu 9 i Zasobu 2 (Pakiet)", + "sw-update-strategy-package-uri":"Automatycznie generuj unikalny adres URL CoAP do pobrania pakietu i przesyłaj aktualizację oprogramowania za pomocą Obiektu 9 i Zasób 3 (Pakiet URI)", + "fw-update-resource":"Zasób CoAP aktualizacji firmware'u", + "fw-update-resource-required":"Zasób CoAP aktualizacji firmware'u jest wymagany.", + "sw-update-resource":"Zasób CoAP aktualizacji oprogramowania", + "sw-update-resource-required":"Zasób CoAP aktualizacji oprogramowania jest wymagany.", + "config-json-tab":"Profil konfiguracji urządzenia Json", + "attributes-name":{ + "min-period":"Minimalny okres", + "max-period":"Maksymalny okres", + "greater-than":"Większe niż", + "less-than":"Mniejsze niż", + "step":"Krok", + "min-evaluation-period":"Minimalny okres oceny", + "max-evaluation-period":"Maksymalny okres oceny" + }, + "composite-operations-support":"Obsługuje operacje złożone Odczyt/Zapis/Obserwacja" + }, + "snmp":{ + "add-communication-config":"Dodaj konfigurację komunikacji", + "add-mapping":"Dodaj mapowanie", + "authentication-passphrase":"Hasło uwierzytelniania", + "authentication-passphrase-required":"Wymagane hasło uwierzytelniania.", + "authentication-protocol":"Protokół uwierzytelniania", + "authentication-protocol-required":"Wymagany protokół uwierzytelniania.", + "communication-configs":"Konfiguracje komunikacji", + "community":"Ciąg społeczności", + "community-required":"Wymagany ciąg społeczności.", + "context-name":"Nazwa kontekstu", + "data-key":"Klucz danych", + "data-key-required":"Wymagany klucz danych.", + "data-type":"Typ danych", + "data-type-required":"Wymagany typ danych.", + "engine-id":"ID silnika", + "host":"Host", + "host-required":"Wymagany host.", + "oid":"OID", + "oid-pattern":"Nieprawidłowy format OID.", + "oid-required":"Wymagany OID.", + "please-add-communication-config":"Proszę dodać konfigurację komunikacji", + "please-add-mapping-config":"Proszę dodać konfigurację mapowania", + "port":"Port", + "port-format":"Nieprawidłowy format portu.", + "port-required":"Wymagany port.", + "privacy-passphrase":"Hasło prywatności", + "privacy-passphrase-required":"Wymagane hasło prywatności.", + "privacy-protocol":"Protokół prywatności", + "privacy-protocol-required":"Wymagany protokół prywatności.", + "protocol-version":"Wersja protokołu", + "protocol-version-required":"Wymagana wersja protokołu.", + "querying-frequency":"Częstotliwość zapytań, ms", + "querying-frequency-invalid-format":"Częstotliwość zapytań musi być liczbą całkowitą dodatnią.", + "querying-frequency-required":"Wymagana częstotliwość zapytań.", + "retries":"Ponowienia", + "retries-invalid-format":"Liczba ponowień musi być liczbą całkowitą dodatnią.", + "retries-required":"Wymagane ponowienia.", + "scope":"Zakres", + "scope-required":"Wymagany zakres.", + "security-name":"Nazwa zabezpieczeń", + "security-name-required":"Wymagana nazwa zabezpieczeń.", + "timeout-ms":"Limit czasu, ms", + "timeout-ms-invalid-format":"Limit czasu musi być liczbą całkowitą dodatnią.", + "timeout-ms-required":"Wymagany limit czasu.", + "user-name":"Nazwa użytkownika", + "user-name-required":"Wymagana nazwa użytkownika." + } + }, + "dialog":{ + "close":"Zamknij okno dialogowe", + "error-message-title":"Wiadomość o błędzie:", + "error-details-title":"Szczegóły błędu" + }, + "direction":{ + "column":"Kolumna", + "row":"Wiersz" + }, + "edge":{ + "edge":"Krawędź", + "edge-instances":"Instancje krawędzi", + "instances":"Instancje", + "edge-file":"Plik krawędzi", + "name-max-length":"Nazwa powinna być krótsza niż 256", + "label-max-length":"Etykieta powinna być krótsza niż 256", + "type-max-length":"Typ powinien być krótszy niż 256", + "management":"Zarządzanie krawędzią", + "no-edges-matching":"Brak krawędzi pasujących do '{{entity}}'", + "add":"Dodaj krawędź", + "no-edges-text":"Brak znalezionych krawędzi", + "edge-details":"Szczegóły krawędzi", + "add-edge-text":"Dodaj nową krawędź", + "delete":"Usuń krawędź", + "delete-edge-title":"Czy na pewno chcesz usunąć krawędź '{{edgeName}}'?", + "delete-edge-text":"Bądź ostrożny, po potwierdzeniu krawędź i wszystkie związane z nią dane staną się nieodwracalnie utracone.", + "delete-edges-title":"Czy na pewno chcesz usunąć krawędź { count, plural, =1 {1 krawędź} other {# krawędzi} }?", + "delete-edges-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane krawędzie zostaną usunięte, a wszystkie związane z nimi dane staną się nieodwracalnie utracone.", + "name":"Nazwa", + "name-starts-with":"Nazwa krawędzi zaczyna się od", + "name-required":"Nazwa jest wymagana.", + "description":"Opis", + "details":"Szczegóły", + "events":"Zdarzenia", + "copy-id":"Kopiuj identyfikator krawędzi", + "id-copied-message":"Identyfikator krawędzi został skopiowany do schowka", + "sync":"Synchronizuj krawędź", + "edge-required":"Wymagana krawędź", + "edge-type":"Typ krawędzi", + "edge-type-required":"Wymagany jest typ krawędzi.", + "event-action":"Akcja zdarzenia", + "entity-id":"Identyfikator jednostki", + "select-edge-type":"Wybierz typ krawędzi", + "assign-to-customer":"Przypisz do klienta", + "assign-to-customer-text":"Proszę wybrać klienta, do którego przypisane będą krawędzie", + "assign-edge-to-customer":"Przypisz krawędź do klienta", + "assign-edge-to-customer-text":"Proszę wybrać krawędzie do przypisania do klienta", + "assignedToCustomer":"Przypisane do klienta", + "edge-public":"Krawędź jest publiczna", + "assigned-to-customer":"Przypisane do: {{customerTitle}}", + "unassign-from-customer":"Odłącz od klienta", + "unassign-edge-title":"Czy na pewno chcesz odłączyć krawędź '{{edgeName}}'?", + "unassign-edge-text":"Po potwierdzeniu krawędź zostanie odłączona i nie będzie dostępna dla klienta.", + "unassign-edges-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 krawędź} other {# krawędzie} }?", + "unassign-edges-text":"Po potwierdzeniu wszystkie wybrane krawędzie zostaną odłączone i nie będą dostępne dla klienta.", + "make-public":"Udostępnij publicznie", + "make-public-edge-title":"Czy na pewno chcesz udostępnić publicznie krawędź '{{edgeName}}'?", + "make-public-edge-text":"Po potwierdzeniu krawędź i wszystkie jej dane zostaną udostępnione publicznie i będą dostępne dla innych.", + "make-private":"Udostępnij prywatnie", + "public":"Publiczne", + "make-private-edge-title":"Czy na pewno chcesz udostępnić prywatnie krawędź '{{edgeName}}'?", + "make-private-edge-text":"Po potwierdzeniu krawędź i wszystkie jej dane zostaną udostępnione prywatnie i nie będą dostępne dla innych.", + "import":"Importuj krawędź", + "install-connect-instructions":"Instrukcje instalacji i połączenia", + "install-connect-instructions-edge-created":"Krawędź utworzona! Sprawdź instrukcje instalacji i połączenia", + "loading-edge-instructions":"Ładowanie instrukcji krawędzi...", + "label":"Etykieta", + "load-entity-error":"Nie udało się załadować danych. Jednostka została usunięta.", + "assign-new-edge":"Przypisz nową krawędź", + "unassign-from-edge":"Odłącz od krawędzi", + "edge-key":"Klucz krawędzi", + "copy-edge-key":"Kopiuj klucz krawędzi", + "edge-key-copied-message":"Klucz krawędzi został skopiowany do schowka", + "edge-secret":"Sekret krawędzi", + "copy-edge-secret":"Kopiuj sekret krawędzi", + "edge-secret-copied-message":"Sekret krawędzi został skopiowany do schowka", + "manage-assets":"Zarządzaj zasobami", + "manage-devices":"Zarządzaj urządzeniami", + "manage-entity-views":"Zarządzaj widokami jednostek", + "manage-dashboards":"Zarządzaj pulpitem nawigacyjnym", + "manage-rulechains":"Zarządzaj łańcuchami reguł", + "assets":"Zasoby krawędzi", + "devices":"Urządzenia krawędziowe", + "entity-views":"Widoki jednostek krawędziowych", + "dashboard":"Pulpit nawigacyjny krawędzi", + "dashboards":"Panele nawigacyjne krawędziowe", + "rulechain-templates":"Szablony łańcucha reguł", + "edge-rulechain-templates":"Szablony łańcucha reguł krawędziowych", + "rulechains":"Łańcuchy reguł krawędziowych", + "search":"Wyszukaj krawędzie", + "selected-edges":"{ count, plural, =1 {1 krawędź} other {# krawędzie} } wybrana", + "any-edge":"Dowolna krawędź", + "no-edge-types-matching":"Brak typów krawędzi pasujących do '{{entitySubtype}}'", + "edge-type-list-empty":"Brak wybranych typów krawędzi.", + "edge-types":"Typy krawędzi", + "enter-edge-type":"Wprowadź typ krawędzi", + "deployed":"Wdrożone", + "pending":"Oczekujące", + "downlinks":"Downlinki", + "no-downlinks-prompt":"Brak znalezionych downlinków", + "sync-process-started-successfully":"Proces synchronizacji został pomyślnie uruchomiony!", + "missing-related-rule-chains-title":"Krawędź ma brakujące powiązane łańcuchy reguł", + "missing-related-rule-chains-text":"Przypisane do krawędzi łańcuchy reguł używają węzłów reguł, które przekazują wiadomości do łańcuchów reguł, które nie są przypisane do tej krawędzi.

    Lista brakujących łańcuchów reguł:
    {{missingRuleChains}}", + "upgrade-instructions":"Instrukcje aktualizacji", + "widget-datasource-error":"Ten widżet obsługuje tylko źródło danych krawędziowe" + }, + "edge-event":{ + "type-dashboard":"Panel nawigacyjny", + "type-asset":"Zasób", + "type-device":"Urządzenie", + "type-device-profile":"Profil urządzenia", + "type-asset-profile":"Profil zasobu", + "type-entity-view":"Widok jednostki", + "type-alarm":"Alarm", + "type-rule-chain":"Łańcuch reguł", + "type-rule-chain-metadata":"Metadane łańcucha reguł", + "type-edge":"Krawędź", + "type-user":"Użytkownik", + "type-tenant":"Najemca", + "type-tenant-profile":"Profil najemcy", + "type-customer":"Klient", + "type-relation":"Relacja", + "type-widgets-bundle":"Pakiet widżetów", + "type-widgets-type":"Typ widżetu", + "type-admin-settings":"Ustawienia administratora", + "type-ota-package":"Pakiet OTA", + "type-queue":"Kolejka", + "action-type-added":"Dodane", + "action-type-deleted":"Usunięte", + "action-type-updated":"Zaktualizowane", + "action-type-post-attributes":"Opublikuj atrybuty", + "action-type-attributes-updated":"Zaktualizowane atrybuty", + "action-type-attributes-deleted":"Usunięte atrybuty", + "action-type-timeseries-updated":"Zaktualizowane serie czasowe", + "action-type-credentials-updated":"Zaktualizowane dane uwierzytelniające", + "action-type-assigned-to-customer":"Przypisane do klienta", + "action-type-unassigned-from-customer":"Odłączone od klienta", + "action-type-relation-add-or-update":"Dodaj lub aktualizuj relację", + "action-type-relation-deleted":"Usunięta relacja", + "action-type-rpc-call":"Wywołanie RPC", + "action-type-alarm-ack":"Potwierdzenie alarmu", + "action-type-alarm-clear":"Wyczyść alarm", + "action-type-alarm-assigned":"Przypisany alarm", + "action-type-alarm-unassigned":"Odłączony alarm", + "action-type-assigned-to-edge":"Przypisane do krawędzi", + "action-type-unassigned-from-edge":"Odłączone od krawędzi", + "action-type-credentials-request":"Żądanie danych uwierzytelniających", + "action-type-entity-merge-request":"Żądanie scalenia jednostki" + }, + "error":{ + "unable-to-connect":"Nie można połączyć się z serwerem! Sprawdź swoje połączenie internetowe.", + "unhandled-error-code":"Niestandardowy kod błędu: {{errorCode}}", + "unknown-error":"Nieznany błąd" + }, + "entity":{ + "entity":"Encja", + "entities":"Encje", + "entities-count":"Liczba encji", + "alarms-count":"Liczba alarmów", + "aliases":"Aliasy encji", + "aliases-short":"Aliasy", + "entity-alias":"Alias encji", + "unable-delete-entity-alias-title":"Nie można usunąć aliasu encji", + "unable-delete-entity-alias-text":"Alias encji '{{entityAlias}}' nie może zostać usunięty, ponieważ jest używany przez następujące widżety:
    {{widgetsList}}", + "duplicate-alias-error":"Znaleziono zduplikowany alias '{{alias}}'.
    Aleasy encji muszą być unikalne w obrębie deski rozdzielczej.", + "missing-entity-filter-error":"Brakuje filtru dla aliasu '{{alias}}'.", + "configure-alias":"Skonfiguruj alias '{{alias}}'", + "alias":"Alias", + "alias-required":"Wymagany jest alias encji.", + "remove-alias":"Usuń alias encji", + "add-alias":"Dodaj alias encji", + "entity-list":"Lista encji", + "entity-type":"Typ encji", + "entity-types":"Typy encji", + "entity-type-list":"Lista typów encji", + "any-entity":"Dowolna encja", + "add-entity-type":"Dodaj typ encji", + "enter-entity-type":"Wprowadź typ encji", + "no-entities-matching":"Nie znaleziono encji pasujących do '{{entity}}'.", + "no-entity-types-matching":"Nie znaleziono typów encji pasujących do '{{entityType}}'.", + "name-starts-with":"Wyrażenie nazwy", + "help-text":"Użyj '%', zgodnie z potrzebą: '%entity_name_contains%', '%entity_name_ends', 'entity_starts_with'.", + "use-entity-name-filter":"Użyj filtra", + "entity-list-empty":"Nie wybrano żadnych encji.", + "entity-type-list-required":"Należy wybrać co najmniej jeden typ encji.", + "entity-name-filter-required":"Wymagany jest filtr nazwy encji.", + "entity-name-filter-no-entity-matched":"Nie znaleziono encji rozpoczynających się od '{{entity}}'.", + "all-subtypes":"Wszystkie", + "select-entities":"Wybierz encje", + "no-aliases-found":"Nie znaleziono aliasów.", + "no-alias-matching":"'{{alias}}' nie znaleziono.", + "create-new-alias":"Utwórz nowy!", + "create-new":"Utwórz nowy", + "key":"Klucz", + "key-name":"Nazwa klucza", + "no-keys-found":"Nie znaleziono kluczy.", + "no-key-matching":"'{{key}}' nie znaleziono.", + "create-new-key":"Utwórz nowy!", + "type":"Typ", + "type-required":"Wymagany jest typ encji.", + "type-device":"Urządzenie", + "type-devices":"Urządzenia", + "list-of-devices":"{ count, plural, =1 {Jedno urządzenie} other {Lista # urządzeń} }", + "device-name-starts-with":"Urządzenia, których nazwy zaczynają się od '{{prefix}}'", + "type-device-profile":"Profil urządzenia", + "type-device-profiles":"Profile urządzeń", + "clear-selected-profiles":"Wyczyść wybrane profile", + "list-of-device-profiles":"{ count, plural, =1 {Jeden profil urządzenia} other {Lista # profili urządzeń} }", + "device-profile-name-starts-with":"Profile urządzeń, których nazwy zaczynają się od '{{prefix}}'", + "type-asset-profile":"Profil zasobów", + "type-asset-profiles":"Profile zasobów", + "list-of-asset-profiles":"{ count, plural, =1 {Jeden profil zasobów} other {Lista # profili zasobów} }", + "asset-profile-name-starts-with":"Profile zasobów, których nazwy zaczynają się od '{{prefix}}'", + "type-asset":"Zasób", + "type-assets":"Zasoby", + "list-of-assets":"{ count, plural, =1 {Jeden zasób} other {Lista # zasobów} }", + "asset-name-starts-with":"Zasoby, których nazwy zaczynają się od '{{prefix}}'", + "type-entity-view":"Widok encji", + "type-entity-views":"Widoki encji", + "list-of-entity-views":"{ count, plural, =1 {Jeden widok encji} other {Lista # widoków encji} }", + "entity-view-name-starts-with":"Widoki encji, których nazwy zaczynają się od '{{prefix}}'", + "type-rule":"Reguła", + "type-rules":"Reguły", + "list-of-rules":"{ count, plural, =1 {Jedna reguła} other {Lista # reguł} }", + "rule-name-starts-with":"Reguły, których nazwy zaczynają się od '{{prefix}}'", + "type-plugin":"Wtyczka", + "type-plugins":"Wtyczki", + "list-of-plugins":"{ count, plural, =1 {Jedna wtyczka} other {Lista # wtyczek} }", + "plugin-name-starts-with":"Wtyczki, których nazwy zaczynają się od '{{prefix}}'", + "type-tenant":"Najemca", + "type-tenants":"Najemcy", + "list-of-tenants":"{ count, plural, =1 {Jeden najemca} other {Lista # najemców} }", + "tenant-name-starts-with":"Najemcy, których nazwy zaczynają się od '{{prefix}}'", + "type-tenant-profile":"Profil najemcy", + "type-tenant-profiles":"Profile najemcy", + "list-of-tenant-profiles":"{ count, plural, =1 {Jeden profil najemcy} other {Lista # profili najemcy} }", + "tenant-profile-name-starts-with":"Profile najemcy, których nazwy zaczynają się od '{{prefix}}'", + "type-customer":"Klient", + "type-customers":"Klienci", + "list-of-customers":"{ count, plural, =1 {Jeden klient} other {Lista # klientów} }", + "customer-name-starts-with":"Klienci, których nazwy zaczynają się od '{{prefix}}'", + "type-user":"Użytkownik", + "type-users":"Użytkownicy", + "list-of-users":"{ count, plural, =1 {Jeden użytkownik} other {Lista # użytkowników} }", + "user-name-starts-with":"Użytkownicy, których nazwy zaczynają się od '{{prefix}}'", + "type-dashboard":"Deska rozdzielcza", + "type-dashboards":"Deski rozdzielcze", + "list-of-dashboards":"{ count, plural, =1 {Jedna deska rozdzielcza} other {Lista # desek rozdzielczych} }", + "dashboard-name-starts-with":"Deski rozdzielcze, których nazwy zaczynają się od '{{prefix}}'", + "type-alarm":"Alarm", + "type-alarms":"Alarmy", + "list-of-alarms":"{ count, plural, =1 {Jeden alarm} other {Lista # alarmów} }", + "alarm-name-starts-with":"Alarmy, których nazwy zaczynają się od '{{prefix}}'", + "type-rulechain":"Łańcuch reguł", + "type-rulechains":"Łańcuchy reguł", + "list-of-rulechains":"{ count, plural, =1 {Jeden łańcuch reguł} other {Lista # łańcuchów reguł} }", + "rulechain-name-starts-with":"Łańcuchy reguł, których nazwy zaczynają się od '{{prefix}}'", + "type-rulenode":"Węzeł reguły", + "type-rulenodes":"Węzły reguł", + "list-of-rulenodes":"{ count, plural, =1 {Jeden węzeł reguły} other {Lista # węzłów reguł} }", + "rulenode-name-starts-with":"Węzły reguł, których nazwy zaczynają się od '{{prefix}}'", + "type-current-customer":"Bieżący Klient", + "type-current-tenant":"Bieżący Najemca", + "type-current-user":"Bieżący Użytkownik", + "type-current-user-owner":"Bieżący Właściciel Użytkownika", + "type-widgets-bundle":"Zestaw Widżetów", + "type-widgets-bundles":"Zestawy Widżetów", + "list-of-widgets-bundles":"{ count, plural, =1 {Jeden zestaw widżetów} other {Lista # zestawów widżetów} }", + "type-widget":"Widżet", + "type-widgets":"Widżety", + "list-of-widgets":"{ count, plural, =1 {Jeden widżet} other {Lista # widżetów} }", + "search":"Szukaj jednostek", + "selected-entities":"{ count, plural, =1 {1 jednostka} other {# jednostki} } wybranych", + "entity-name":"Nazwa jednostki", + "entity-label":"Etykieta jednostki", + "details":"Szczegóły jednostki", + "no-entities-prompt":"Nie znaleziono jednostek", + "no-data":"Brak danych do wyświetlenia", + "columns-to-display":"Kolumny do wyświetlenia", + "type-api-usage-state":"Stan korzystania z API", + "type-edge":"Edge", + "type-edges":"Edges", + "list-of-edges":"{ count, plural, =1 {Jeden edge} other {Lista # edges} }", + "edge-name-starts-with":"Edges, których nazwy zaczynają się od '{{prefix}}'", + "type-tb-resource":"Zasób", + "type-tb-resources":"Zasoby", + "list-of-tb-resources":"{ count, plural, =1 {Jeden zasób} other {Lista # zasobów} }", + "type-ota-package":"Pakiet OTA", + "type-rpc":"RPC", + "type-queue":"Kolejka", + "type-notification":"Powiadomienie", + "type-notification-rule":"Reguła powiadomień", + "type-notification-rules":"Reguły powiadomień", + "list-of-notification-rules":"{ count, plural, =1 {Jedna reguła powiadomień} other {Lista # reguł powiadomień} }", + "type-notification-target":"Odbiorca powiadomienia", + "type-notification-targets":"Odbiorcy powiadomień", + "list-of-notification-targets":"{ count, plural, =1 {Jeden odbiorca powiadomienia} other {Lista # odbiorców powiadomień} }", + "type-notification-request":"Żądanie powiadomienia", + "type-notification-template":"Szablon powiadomienia", + "type-notification-templates":"Szablony powiadomień", + "list-of-notification-templates":"{ count, plural, =1 {Jeden szablon powiadomienia} other {Lista # szablonów powiadomień} }" + }, + "entity-field":{ + "created-time":"Czas utworzenia", + "name":"Nazwa", + "type":"Typ", + "first-name":"Imię", + "last-name":"Nazwisko", + "email":"E-mail", + "title":"Tytuł", + "country":"Kraj", + "state":"Stan", + "city":"Miasto", + "address":"Adres", + "address2":"Adres 2", + "zip":"Kod pocztowy", + "phone":"Telefon", + "label":"Etykieta" + }, + "entity-view":{ + "entity-view":"Widok encji", + "entity-view-required":"Widok encji jest wymagany.", + "entity-views":"Widoki encji", + "management":"Zarządzanie widokiem encji", + "view-entity-views":"Wyświetl widoki encji", + "entity-view-alias":"Alias widoku encji", + "aliases":"Aliasy widoku encji", + "no-alias-matching":"'{{alias}}' nie znaleziono.", + "no-aliases-found":"Nie znaleziono aliasów.", + "no-key-matching":"'{{key}}' nie znaleziono.", + "no-keys-found":"Nie znaleziono kluczy.", + "create-new-alias":"Utwórz nowy!", + "create-new-key":"Utwórz nowy!", + "duplicate-alias-error":"Znaleziono zduplikowany alias '{{alias}}'.
    Aliasy widoku encji muszą być unikalne w obrębie panelu.", + "configure-alias":"Konfiguruj alias '{{alias}}'", + "no-entity-views-matching":"Brak widoków encji pasujących do '{{entity}}'.", + "public":"Publiczny", + "alias":"Alias", + "alias-required":"Wymagany jest alias widoku encji.", + "remove-alias":"Usuń alias widoku encji", + "add-alias":"Dodaj alias widoku encji", + "name-starts-with":"Wyrażenie nazwy widoku encji", + "help-text":"Użyj '%' według potrzeb: '%entity-view_name_contains%', '%entity-view_name_ends', 'entity-view_starts_with'.", + "entity-view-list":"Lista widoków encji", + "use-entity-view-name-filter":"Użyj filtra", + "entity-view-list-empty":"Brak wybranych widoków encji.", + "entity-view-name-filter-required":"Wymagany jest filtr nazwy widoku encji.", + "entity-view-name-filter-no-entity-view-matched":"Nie znaleziono widoków encji rozpoczynających się od '{{entityView}}'.", + "add":"Dodaj widok encji", + "entity-view-public":"Widok encji jest publiczny", + "assign-to-customer":"Przypisz do klienta", + "assign-entity-view-to-customer":"Przypisz Widoki Encji Do Klienta", + "assign-entity-view-to-customer-text":"Wybierz widoki encji do przypisania do klienta", + "assign-entity-view-to-edge-title":"Przypisz Widoki Encji Do Krawędzi", + "no-entity-views-text":"Nie znaleziono widoków encji", + "assign-to-customer-text":"Wybierz klienta, do którego chcesz przypisać widoki encji", + "entity-view-details":"Szczegóły widoku encji", + "add-entity-view-text":"Dodaj nowy widok encji", + "delete":"Usuń widok encji", + "assign-entity-views":"Przypisz widoki encji", + "assign-entity-views-text":"Przypisz { count, plural, =1 {1 widok encji} other {# widoków encji} } do klienta", + "delete-entity-views":"Usuń widoki encji", + "unassign-from-customer":"Odłącz od klienta", + "unassign-entity-views":"Odłącz widoki encji", + "unassign-entity-views-action-title":"Odłącz { count, plural, =1 {1 widok encji} other {# widoków encji} } od klienta", + "assign-new-entity-view":"Przypisz nowy widok encji", + "delete-entity-view-title":"Czy na pewno chcesz usunąć widok encji '{{entityViewName}}'?", + "delete-entity-view-text":"Bądź ostrożny, po potwierdzeniu widok encji i wszystkie związane z nim dane staną się nieodwracalnie utracone.", + "delete-entity-views-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 widok encji} other {# widoków encji} }?", + "delete-entity-views-action-title":"Usuń { count, plural, =1 {1 widok encji} other {# widoków encji} }", + "delete-entity-views-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane widoki encji zostaną usunięte, a wszystkie związane z nimi dane staną się nieodwracalnie utracone.", + "unassign-entity-view-title":"Czy na pewno chcesz odłączyć widok encji '{{entityViewName}}'?", + "unassign-entity-view-text":"Po potwierdzeniu widok encji zostanie odłączony i nie będzie dostępny dla klienta.", + "unassign-entity-view":"Odłącz widok encji", + "unassign-entity-views-title":"Czy na pewno chcesz odłączyć { count, plural, =1 {1 widok encji} other {# widoków encji} }?", + "unassign-entity-views-text":"Po potwierdzeniu wszystkie wybrane widoki encji zostaną odłączone i nie będą dostępne dla klienta.", + "entity-view-type":"Typ widoku encji", + "entity-view-type-required":"Wymagany jest typ widoku encji.", + "select-entity-view-type":"Wybierz typ widoku encji", + "enter-entity-view-type":"Wprowadź typ widoku encji", + "any-entity-view":"Dowolny widok encji", + "no-entity-view-types-matching":"Brak typów widoków encji pasujących do '{{entitySubtype}}'.", + "entity-view-type-list-empty":"Brak wybranych typów widoków encji.", + "entity-view-types":"Typy widoków encji", + "created-time":"Czas utworzenia", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "name-max-length":"Nazwa powinna mieć mniej niż 256 znaków.", + "type-max-length":"Typ widoku jednostki powinien mieć mniej niż 256 znaków.", + "description":"Opis", + "events":"Zdarzenia", + "details":"Szczegóły", + "copyId":"Kopiuj identyfikator widoku jednostki", + "idCopiedMessage":"Identyfikator widoku jednostki został skopiowany do schowka.", + "assignedToCustomer":"Przypisane do klienta", + "unable-entity-view-device-alias-title":"Nie można usunąć aliasu widoku jednostki", + "unable-entity-view-device-alias-text":"Alias urządzenia '{{entityViewAlias}}' nie może zostać usunięty, ponieważ jest używany przez następujące widgety:
    {{widgetsList}}", + "select-entity-view":"Wybierz widok jednostki", + "make-public":"Udostępnij widok jednostki publicznie", + "make-private":"Udostępnij widok jednostki prywatnie", + "start-date":"Data rozpoczęcia", + "start-ts":"Czas rozpoczęcia", + "end-date":"Data zakończenia", + "end-ts":"Czas zakończenia", + "date-limits":"Ograniczenia dat", + "client-attributes":"Atrybuty klienta", + "shared-attributes":"Współdzielone atrybuty", + "server-attributes":"Atrybuty serwera", + "timeseries":"Szereg czasowy", + "client-attributes-placeholder":"Atrybuty klienta", + "shared-attributes-placeholder":"Współdzielone atrybuty", + "server-attributes-placeholder":"Atrybuty serwera", + "timeseries-placeholder":"Szereg czasowy", + "target-entity":"Docelowa jednostka", + "attributes-propagation":"Propagacja atrybutów", + "attributes-propagation-hint":"Widok jednostki będzie automatycznie kopiować określone atrybuty z Docelowej Jednostki za każdym razem, gdy zapisujesz lub aktualizujesz ten widok jednostki. Ze względów wydajnościowych atrybuty Docelowej Jednostki nie są propagowane do widoku jednostki przy każdej zmianie atrybutu. Możesz włączyć automatyczną propagację, konfigurując węzeł reguły „kopiuj do widoku” w swoim łańcuchu reguł i łącząc wiadomości „Post attributes” i „Attributes Updated” z nowym węzłem reguły.", + "timeseries-data":"Dane szeregu czasowego", + "timeseries-data-hint":"Skonfiguruj klucze danych szeregu czasowego Docelowej Jednostki, które będą dostępne w widoku jednostki. Te dane szeregu czasowego są tylko do odczytu.", + "search":"Szukaj widoków jednostki", + "selected-entity-views":"{ count, plural, =1 {1 widok jednostki} other {# widoki jednostki} } wybrano", + "make-public-entity-view-title":"Czy na pewno chcesz udostępnić widok jednostki '{{entityViewName}}' publicznie?", + "make-public-entity-view-text":"Po potwierdzeniu widok jednostki i wszystkie jego dane staną się publiczne i dostępne dla innych.", + "make-private-entity-view-title":"Czy na pewno chcesz uczynić widok jednostki '{{entityViewName}}' prywatnym?", + "make-private-entity-view-text":"Po potwierdzeniu widok jednostki i wszystkie jego dane staną się prywatne i nie będą dostępne dla innych.", + "assign-entity-view-to-edge":"Przypisz widok jednostki do urządzenia", + "assign-entity-view-to-edge-text":"Wybierz widoki jednostki do przypisania do urządzenia", + "unassign-entity-view-from-edge-title":"Czy na pewno chcesz odpiąć widok jednostki '{{entityViewName}}'?", + "unassign-entity-view-from-edge-text":"Po potwierdzeniu widok jednostki zostanie odpięty i nie będzie dostępny dla urządzenia.", + "unassign-entity-views-from-edge-action-title":"Odpiąć { count, plural, =1 {1 widok jednostki} other {# widoki jednostki} } od urządzenia", + "unassign-entity-view-from-edge":"Odpiąć widok jednostki", + "unassign-entity-views-from-edge-title":"Czy na pewno chcesz odpiąć { count, plural, =1 {1 widok jednostki} other {# widoki jednostki} }?", + "unassign-entity-views-from-edge-text":"Po potwierdzeniu wszystkie wybrane widoki jednostki zostaną odpięte i nie będą dostępne dla urządzenia." + }, + "event":{ + "event-type":"Typ zdarzenia", + "events-filter":"Filtr zdarzeń", + "clean-events":"Wyczyść zdarzenia", + "type-error":"Błąd", + "type-lc-event":"Zdarzenie cyklu życia", + "type-stats":"Statystyki", + "type-debug-rule-node":"Debug", + "type-debug-rule-chain":"Debug", + "no-events-prompt":"Nie znaleziono zdarzeń", + "error":"Błąd", + "alarm":"Alarm", + "event-time":"Czas zdarzenia", + "server":"Serwer", + "body":"Treść", + "method":"Metoda", + "type":"Typ", + "message":"Wiadomość", + "message-id":"Identyfikator wiadomości", + "copy-message-id":"Kopiuj identyfikator wiadomości", + "message-type":"Typ wiadomości", + "data-type":"Typ danych", + "relation-type":"Typ relacji", + "metadata":"Metadane", + "data":"Dane", + "event":"Zdarzenie", + "status":"Status", + "success":"Sukces", + "failed":"Niepowodzenie", + "messages-processed":"Wiadomości przetworzone", + "max-messages-processed":"Maksymalna ilość wiadomości przetworzonych", + "min-messages-processed":"Minimalna ilość wiadomości przetworzonych", + "errors-occurred":"Wystąpiły błędy", + "max-errors-occurred":"Maksymalna ilość wystąpiłych błędów", + "min-errors-occurred":"Minimalna ilość wystąpiłych błędów", + "min-value":"Minimalna wartość to 0.", + "all-events":"Wszystkie", + "has-error":"Zawiera błąd", + "entity-id":"Identyfikator jednostki", + "copy-entity-id":"Kopiuj identyfikator jednostki", + "entity-type":"Typ jednostki", + "clear-filter":"Wyczyść filtr", + "clear-request-title":"Wyczyść wszystkie zdarzenia", + "clear-request-text":"Czy na pewno chcesz wyczyścić wszystkie zdarzenia?", + "started":"Rozpoczęte", + "updated":"Zaktualizowane", + "stopped":"Zatrzymane" + }, + "extension":{ + "extensions":"Rozszerzenia", + "selected-extensions":"{ count, plural, =1 {1 rozszerzenie} other {# rozszerzeń} } wybranych", + "type":"Typ", + "key":"Klucz", + "value":"Wartość", + "id":"Identyfikator", + "extension-id":"Identyfikator rozszerzenia", + "extension-type":"Typ rozszerzenia", + "transformer-json":"JSON *", + "unique-id-required":"Identyfikator rozszerzenia już istnieje.", + "delete":"Usuń rozszerzenie", + "add":"Dodaj rozszerzenie", + "edit":"Edytuj rozszerzenie", + "delete-extension-title":"Czy na pewno chcesz usunąć rozszerzenie '{{extensionId}}'?", + "delete-extension-text":"Bądź ostrożny, po potwierdzeniu rozszerzenie i wszystkie związane z nim dane staną się nieodwracalnie utracone.", + "delete-extensions-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 rozszerzenie} other {# rozszerzeń} }?", + "delete-extensions-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane rozszerzenia zostaną usunięte.", + "converters":"Konwertery", + "converter-id":"Identyfikator konwertera", + "configuration":"Konfiguracja", + "converter-configurations":"Konfiguracje konwertera", + "token":"Token bezpieczeństwa", + "add-converter":"Dodaj konwerter", + "add-config":"Dodaj konfigurację konwertera", + "device-name-expression":"Wyrażenie nazwy urządzenia", + "device-type-expression":"Wyrażenie typu urządzenia", + "custom":"Niestandardowe", + "to-double":"Do Double", + "transformer":"Transformator", + "json-required":"Wymagany jest JSON transformatora.", + "json-parse":"Nie można sparsować JSON transformatora.", + "attributes":"Atrybuty", + "add-attribute":"Dodaj atrybut", + "add-map":"Dodaj element mapowania", + "timeseries":"Szereg czasowy", + "add-timeseries":"Dodaj szereg czasowy", + "field-required":"Pole jest wymagane", + "brokers":"Brokerzy", + "add-broker":"Dodaj brokera", + "host":"Host", + "port":"Port", + "port-range":"Port powinien mieć zakres od 1 do 65535.", + "ssl":"SSL", + "credentials":"Dane uwierzytelniające", + "username":"Nazwa użytkownika", + "password":"Hasło", + "retry-interval":"Interwał ponawiania w milisekundach", + "anonymous":"Anonimowy", + "basic":"Podstawowy", + "pem":"PEM", + "ca-cert":"Plik certyfikatu CA *", + "private-key":"Plik klucza prywatnego *", + "cert":"Plik certyfikatu *", + "no-file":"Nie wybrano pliku.", + "drop-file":"Upuść plik lub kliknij, aby wybrać plik do przesłania.", + "mapping":"Mapowanie", + "topic-filter":"Filtr tematów", + "converter-type":"Typ konwertera", + "converter-json":"Json", + "json-name-expression":"Wyrażenie nazwy urządzenia JSON", + "topic-name-expression":"Wyrażenie nazwy tematu urządzenia", + "json-type-expression":"Wyrażenie typu urządzenia JSON", + "topic-type-expression":"Wyrażenie typu tematu urządzenia", + "attribute-key-expression":"Wyrażenie klucza atrybutu", + "attr-json-key-expression":"Wyrażenie klucza JSON atrybutu", + "attr-topic-key-expression":"Wyrażenie klucza tematu atrybutu", + "request-id-expression":"Wyrażenie identyfikatora żądania", + "request-id-json-expression":"Wyrażenie identyfikatora JSON żądania", + "request-id-topic-expression":"Wyrażenie identyfikatora tematu żądania", + "response-topic-expression":"Wyrażenie tematu odpowiedzi", + "value-expression":"Wyrażenie wartości", + "topic":"Temat", + "timeout":"Timeout w milisekundach", + "converter-json-required":"Wymagany jest JSON konwertera.", + "converter-json-parse":"Nie można sparsować JSON konwertera.", + "filter-expression":"Wyrażenie filtru", + "connect-requests":"Żądania połączenia", + "add-connect-request":"Dodaj żądanie połączenia", + "disconnect-requests":"Żądania rozłączenia", + "add-disconnect-request":"Dodaj żądanie rozłączenia", + "attribute-requests":"Żądania atrybutów", + "add-attribute-request":"Dodaj żądanie atrybutu", + "attribute-updates":"Aktualizacje atrybutów", + "add-attribute-update":"Dodaj aktualizację atrybutu", + "server-side-rpc":"RPC po stronie serwera", + "add-server-side-rpc-request":"Dodaj żądanie RPC po stronie serwera", + "device-name-filter":"Filtr nazwy urządzenia", + "attribute-filter":"Filtr atrybutu", + "method-filter":"Filtr metody", + "request-topic-expression":"Wyrażenie tematu żądania", + "response-timeout":"Timeout odpowiedzi w milisekundach", + "topic-expression":"Wyrażenie tematu", + "client-scope":"Zakres klienta", + "add-device":"Dodaj urządzenie", + "opc-server":"Serwery OPC", + "opc-add-server":"Dodaj serwer OPC", + "opc-add-server-prompt":"Proszę dodać serwer OPC", + "opc-application-name":"Nazwa aplikacji", + "opc-application-uri":"URI aplikacji", + "opc-scan-period-in-seconds":"Okres skanowania w sekundach", + "opc-security":"Bezpieczeństwo", + "opc-identity":"Tożsamość", + "opc-keystore":"Schronisko klucza", + "opc-type":"Typ", + "opc-keystore-type":"Typ schroniska klucza", + "opc-keystore-location":"Lokalizacja *", + "opc-keystore-password":"Hasło", + "opc-keystore-alias":"Alias", + "opc-keystore-key-password":"Hasło klucza", + "opc-device-node-pattern":"Wzorzec węzła urządzenia", + "opc-device-name-pattern":"Wzorzec nazwy urządzenia", + "modbus-server":"Serwery/slave'y Modbus", + "modbus-add-server":"Dodaj serwer/slave'a Modbus", + "modbus-add-server-prompt":"Proszę dodać serwer/slave'a Modbus", + "modbus-transport":"Transport", + "modbus-tcp-reconnect":"Automatyczne ponowne połączenie", + "modbus-rtu-over-tcp":"RTU przez TCP", + "modbus-port-name":"Nazwa portu szeregowego", + "modbus-encoding":"Kodowanie", + "modbus-parity":"Parzystość", + "modbus-baudrate":"Prędkość transmisji", + "modbus-databits":"Bity danych", + "modbus-stopbits":"Bity stopu", + "modbus-databits-range":"Bity danych powinny mieć zakres od 7 do 8.", + "modbus-stopbits-range":"Bity stopu powinny mieć zakres od 1 do 2.", + "modbus-unit-id":"Identyfikator jednostki", + "modbus-unit-id-range":"Identyfikator jednostki powinien mieć zakres od 1 do 247.", + "modbus-device-name":"Nazwa urządzenia", + "modbus-poll-period":"Okres sondowania (ms)", + "modbus-attributes-poll-period":"Okres sondowania atrybutów (ms)", + "modbus-timeseries-poll-period":"Okres sondowania szeregów czasowych (ms)", + "modbus-poll-period-range":"Okres sondowania powinien być wartością dodatnią.", + "modbus-tag":"Tag", + "modbus-function":"Funkcja", + "modbus-register-address":"Adres rejestru", + "modbus-register-address-range":"Adres rejestru powinien mieć zakres od 0 do 65535.", + "modbus-register-bit-index":"Indeks bitu", + "modbus-register-bit-index-range":"Indeks bitu powinien mieć zakres od 0 do 15.", + "modbus-register-count":"Liczba rejestrów", + "modbus-register-count-range":"Liczba rejestrów powinna być wartością dodatnią.", + "modbus-byte-order":"Kolejność bajtów", + "sync":{ + "status":"Status", + "sync":"Synchronizowane", + "not-sync":"Niesynchronizowane", + "last-sync-time":"Ostatnia synchronizacja", + "not-available":"Niedostępne" + }, + "export-extensions-configuration":"Eksportuj konfigurację rozszerzeń", + "import-extensions-configuration":"Importuj konfigurację rozszerzeń", + "import-extensions":"Importuj rozszerzenia", + "import-extension":"Importuj rozszerzenie", + "export-extension":"Eksportuj rozszerzenie", + "file":"Plik rozszerzeń", + "invalid-file-error":"Błąd - nieprawidłowy plik rozszerzenia" + }, + "feature":{ + "advanced-features":"Zaawansowane funkcje" + }, + "filter":{ + "add":"Dodaj filtr", + "edit":"Edytuj filtr", + "name":"Nazwa filtra", + "name-required":"Wymagana nazwa filtra.", + "duplicate-filter":"Filtr o tej samej nazwie już istnieje.", + "filters":"Filtry", + "unable-delete-filter-title":"Nie można usunąć filtra", + "unable-delete-filter-text":"Nie można usunąć filtra '{{filter}}', ponieważ jest używany przez następujące widżety:
    {{widgetsList}}", + "duplicate-filter-error":"Znaleziono zduplikowany filtr '{{filter}}'.
    Filtry muszą być unikalne w obrębie pulpitu nawigacyjnego.", + "missing-key-filters-error":"Kluczowe filtry są brakujące w filtrze '{{filter}}'.", + "filter":"Filtr", + "editable":"Możliwość edycji", + "no-filters-found":"Nie znaleziono filtrów.", + "no-filter-text":"Brak określonego filtra", + "add-filter-prompt":"Proszę dodać filtr", + "no-filter-matching":"'{{filter}}' nie znaleziono.", + "create-new-filter":"Utwórz nowy!", + "create-new":"Utwórz nowy", + "filter-required":"Filtr jest wymagany.", + "operation":{ + "operation":"Operacja", + "equal":"równa się", + "not-equal":"nie równa się", + "starts-with":"zaczyna się od", + "ends-with":"kończy się na", + "contains":"zawiera", + "not-contains":"nie zawiera", + "greater":"większa niż", + "less":"mniejsza niż", + "greater-or-equal":"większa lub równa", + "less-or-equal":"mniejsza lub równa", + "and":"i", + "or":"lub", + "in":"w", + "not-in":"nie w" + }, + "ignore-case":"ignoruj wielkość liter", + "value":"Wartość", + "remove-filter":"Usuń filtr", + "duplicate-filter-action":"Zduplikuj filtr", + "preview":"Podgląd filtra", + "no-filters":"Brak skonfigurowanych filtrów", + "add-filter":"Dodaj filtr", + "add-complex-filter":"Dodaj filtr złożony", + "add-complex":"Dodaj złożony", + "complex-filter":"Filtr złożony", + "edit-complex-filter":"Edytuj filtr złożony", + "edit-filter-user-params":"Edytuj parametry użytkownika predykatu filtra", + "filter-user-params":"Parametry użytkownika predykatu filtra", + "user-parameters":"Parametry użytkownika", + "display-label":"Etykieta do wyświetlenia", + "autogenerated-label":"Automatycznie generowana etykieta", + "order-priority":"Priorytet porządkowania pola", + "key-filter":"Filtr klucza", + "key-filters":"Filtry klucza", + "key-name":"Nazwa klucza", + "key-name-required":"Wymagana jest nazwa klucza.", + "key-type":{ + "key-type":"Typ klucza", + "attribute":"Atrybut", + "timeseries":"Szereg czasowy", + "entity-field":"Pole encji", + "constant":"Stała", + "client-attribute":"Atrybut klienta", + "server-attribute":"Atrybut serwera", + "shared-attribute":"Atrybut wspólny" + }, + "value-type":{ + "value-type":"Value type", + "string":"String", + "numeric":"Numeric", + "boolean":"Boolean", + "date-time":"Datetime" + }, + "value-type-required":"Wymagany jest typ wartości klucza.", + "key-value-type-change-title":"Czy na pewno chcesz zmienić typ wartości klucza?", + "key-value-type-change-message":"Jeśli potwierdzisz nowy typ wartości, wszystkie wprowadzone filtry klucza zostaną usunięte.", + "no-key-filters":"Brak skonfigurowanych filtrów klucza", + "add-key-filter":"Dodaj filtr klucza", + "remove-key-filter":"Usuń filtr klucza", + "edit-key-filter":"Edytuj filtr klucza", + "date":"Data", + "time":"Czas", + "current-tenant":"Bieżący najemca", + "current-customer":"Bieżący klient", + "current-user":"Bieżący użytkownik", + "current-device":"Bieżące urządzenie", + "default-value":"Wartość domyślna", + "dynamic-source-type":"Typ źródła dynamicznego", + "dynamic-value":"Wartość dynamiczna", + "no-dynamic-value":"Brak wartości dynamicznej", + "source-attribute":"Atrybut źródła", + "switch-to-dynamic-value":"Przełącz na wartość dynamiczną", + "switch-to-default-value":"Przełącz na wartość domyślną", + "inherit-owner":"Dziedzicz od właściciela", + "source-attribute-not-set":"Jeśli atrybut źródła nie jest ustawiony" + }, + "fullscreen":{ + "expand":"Rozwiń na pełny ekran", + "exit":"Wyjdź z trybu pełnoekranowego", + "toggle":"Przełącz tryb pełnoekranowy", + "fullscreen":"Pełny ekran" + }, + "function":{ + "function":"Funkcja" + }, + "gateway":{ + "add-entry":"Dodaj konfigurację", + "advanced":"Zaawansowane", + "checking-device-activity":"Sprawdzanie aktywności urządzenia", + "command":"Komendy Docker", + "command-copied-message":"Komenda Docker została skopiowana do schowka", + "configuration":"Konfiguracja", + "connector-add":"Dodaj nowy konektor", + "connector-enabled":"Włącz konektor", + "connector-name":"Nazwa konektora", + "connector-name-required":"Nazwa konektora jest wymagana.", + "connector-type":"Typ konektora", + "connector-type-required":"Typ konektora jest wymagany.", + "connectors":"Konektory", + "connectors-config":"Konfiguracja konektorów", + "connectors-table-enabled":"Włączony", + "connectors-table-name":"Nazwa", + "connectors-table-type":"Typ", + "connectors-table-status":"Status", + "connectors-table-actions":"Akcje", + "connectors-table-key":"Klucz", + "connectors-table-class":"Klasa", + "rpc-command-send":"Wyślij", + "rpc-command-result":"Odpowiedź", + "rpc-command-edit-params":"Edytuj parametry", + "gateway-configuration":"Konfiguracja ogólna", + "docker-label":"Użyj poniższej instrukcji, aby uruchomić bramę IoT w Docker Compose z uwierzytelnieniem dla wybranego urządzenia", + "install-docker-compose":"Skorzystaj z instrukcji, aby pobrać, zainstalować i skonfigurować Docker Compose", + "download-configuration-file":"Pobierz plik konfiguracyjny", + "download-docker-compose":"Pobierz plik docker-compose.yml dla Twojej bramy", + "launch-gateway":"Uruchom bramę", + "launch-docker-compose":"Uruchom bramę, używając poniższej komendy w terminalu z folderu zawierającego plik docker-compose.yml", + "create-new-gateway":"Utwórz nową bramę", + "create-new-gateway-text":"Jesteś pewien, że chcesz utworzyć nową bramę o nazwie: '{{gatewayName}}'?", + "created-time":"Czas utworzenia", + "configuration-delete-dialog-header":"Konfiguracje zostaną usunięte", + "configuration-delete-dialog-body":"Wyłączenie Konfiguracji Zdalnej jest możliwe tylko przy fizycznym dostępie do Bramy. Wszystkie wcześniejsze konfiguracje zostaną usunięte.

    \nAby wyłączyć konfigurację, wprowadź poniżej nazwę Bramy", + "configuration-delete-dialog-input":"Nazwa Bramy", + "configuration-delete-dialog-input-required":"Nazwa Bramy jest obowiązkowa", + "configuration-delete-dialog-confirm":"Wyłącz", + "delete":"Usuń konfigurację", + "download-tip":"Pobierz plik konfiguracyjny", + "drop-file":"Upuść plik tutaj lub", + "gateway":"Brama", + "gateway-exists":"Urządzenie o tej samej nazwie już istnieje.", + "gateway-name":"Nazwa Bramy", + "gateway-name-required":"Nazwa Bramy jest wymagana.", + "gateway-saved":"Konfiguracja Bramy została pomyślnie zapisana.", + "grpc":"GRPC", + "grpc-keep-alive-timeout":"Limit czasu Keep Alive (w ms)", + "grpc-keep-alive-timeout-required":"Limit czasu Keep Alive jest wymagany", + "grpc-keep-alive-timeout-min":"Limit czasu Keep Alive nie może być mniejszy niż 1", + "grpc-keep-alive-timeout-pattern":"Limit czasu Keep Alive jest nieprawidłowy", + "grpc-keep-alive":"Keep Alive (w ms)", + "grpc-keep-alive-required":"Keep Alive jest wymagany", + "grpc-keep-alive-min":"Keep Alive nie może być mniejszy niż 1", + "grpc-keep-alive-pattern":"Keep Alive jest nieprawidłowy", + "grpc-min-time-between-pings":"Minimalny czas między pingami (w ms)", + "grpc-min-time-between-pings-required":"Minimalny czas między pingami jest wymagany", + "grpc-min-time-between-pings-min":"Minimalny czas między pingami nie może być mniejszy niż 1", + "grpc-min-time-between-pings-pattern":"Minimalny czas między pingami jest nieprawidłowy", + "grpc-min-ping-interval-without-data":"Minimalny interwał pingowania bez danych (w ms)", + "grpc-min-ping-interval-without-data-required":"Minimalny interwał pingowania bez danych jest wymagany", + "grpc-min-ping-interval-without-data-min":"Minimalny interwał pingowania bez danych nie może być mniejszy niż 1", + "grpc-min-ping-interval-without-data-pattern":"Minimalny interwał pingowania bez danych jest nieprawidłowy", + "grpc-max-pings-without-data":"Maksymalna liczba pingów bez danych", + "grpc-max-pings-without-data-required":"Maksymalna liczba pingów bez danych jest wymagana", + "grpc-max-pings-without-data-min":"Maksymalna liczba pingów bez danych nie może być mniejsza niż 1", + "grpc-max-pings-without-data-pattern":"Maksymalna liczba pingów bez danych jest nieprawidłowa", + "inactivity-check-period-seconds":"Okres sprawdzania braku aktywności (w sekundach)", + "inactivity-check-period-seconds-required":"Okres sprawdzania braku aktywności jest wymagany", + "inactivity-check-period-seconds-min":"Okres sprawdzania braku aktywności nie może być mniejszy niż 1", + "inactivity-check-period-seconds-pattern":"Okres sprawdzania braku aktywności jest nieprawidłowy", + "inactivity-timeout-seconds":"Okres braku aktywności (w sekundach)", + "inactivity-timeout-seconds-required":"Okres braku aktywności jest wymagany", + "inactivity-timeout-seconds-min":"Okres braku aktywności nie może być mniejszy niż 1", + "inactivity-timeout-seconds-pattern":"Okres braku aktywności jest nieprawidłowy", + "json-parse":"Nieprawidłowy format JSON.", + "json-required":"To pole nie może być puste.", + "logs":{ + "logs":"Logi", + "days":"dni", + "hours":"godzin", + "minutes":"minut", + "seconds":"sekund", + "date-format":"Format daty", + "date-format-required":"Wymagany format daty", + "log-format":"Format logów", + "log-type":"Typ logu", + "log-format-required":"Wymagany format logów", + "remote":"Zdalne logowanie", + "remote-logs":"Zdalne logi", + "local":"Lokalne logowanie", + "level":"Poziom logowania", + "file-path":"Ścieżka pliku", + "file-path-required":"Wymagana ścieżka pliku", + "saving-period":"Okres zapisywania logów", + "saving-period-min":"Okres zapisywania logów nie może być krótszy niż 1", + "saving-period-required":"Wymagany okres zapisywania logów", + "backup-count":"Liczba kopii zapasowych", + "backup-count-min":"Liczba kopii zapasowych nie może być mniejsza niż 1", + "backup-count-required":"Wymagana liczba kopii zapasowych" + }, + "min-pack-send-delay":"Minimalny opóźnienie wysyłki paczki (w ms)", + "min-pack-send-delay-required":"Wymagane minimalne opóźnienie wysyłki paczki", + "min-pack-send-delay-min":"Minimalne opóźnienie wysyłki paczki nie może być mniejsze niż 0", + "no-connectors":"Brak konektorów", + "no-data":"Brak konfiguracji", + "no-gateway-found":"Nie znaleziono bramy.", + "no-gateway-matching":"Brak dopasowania do '{{item}}'", + "path-logs":"Ścieżka do plików dziennika", + "path-logs-required":"Wymagana ścieżka", + "permit-without-calls":"Zezwalaj na keep alive bez wywołań", + "remote":"Zdalna konfiguracja", + "remote-logging-level":"Poziom logowania", + "remove-entry":"Usuń konfigurację", + "remote-shell":"Zdalny shell", + "remote-configuration":"Zdalna konfiguracja", + "other":"Inne", + "save-tip":"Zapisz plik konfiguracyjny", + "security-type":"Typ zabezpieczeń", + "security-types":{ + "access-token":"Token dostępu", + "username-password":"Nazwa użytkownika i hasło", + "tls":"TLS", + "tls-access-token":"TLS + Token dostępu", + "tls-private-key":"TLS + Klucz prywatny" + }, + "server-port":"Port serwera", + "statistics":{ + "statistic":"Statystyka", + "statistics":"Statystyki", + "statistic-commands-empty":"Brak dostępnych statystyk", + "commands":"Polecenia", + "send-period":"Okres wysyłania statystyk (w sekundach)", + "send-period-required":"Okres wysyłania statystyk jest wymagany", + "send-period-min":"Okres wysyłania statystyk nie może być krótszy niż 60", + "send-period-pattern":"Okres wysyłania statystyk jest nieprawidłowy", + "check-connectors-configuration":"Sprawdź konfigurację łączników (w sekundach)", + "check-connectors-configuration-required":"Sprawdzenie konfiguracji łączników jest wymagane", + "check-connectors-configuration-min":"Sprawdzenie konfiguracji łączników nie może być krótsze niż 1", + "check-connectors-configuration-pattern":"Sprawdzenie konfiguracji łączników jest nieprawidłowe", + "add":"Dodaj polecenie", + "timeout":"Limit czasu", + "timeout-ms":"Limit czasu (w ms)", + "timeout-required":"Limit czasu jest wymagany", + "timeout-min":"Limit czasu nie może być krótszy niż 1", + "timeout-pattern":"Limit czasu jest nieprawidłowy", + "attribute-name":"Nazwa atrybutu", + "attribute-name-required":"Nazwa atrybutu jest wymagana", + "command":"Polecenie", + "command-required":"Polecenie jest wymagane", + "command-pattern":"Polecenie jest nieprawidłowe", + "remove":"Usuń polecenie" + }, + "storage":"Magazyn", + "storage-max-file-records":"Maksymalna liczba rekordów w pliku", + "storage-max-files":"Maksymalna liczba plików", + "storage-max-files-min":"Minimalna liczba wynosi 1.", + "storage-max-files-pattern":"Liczba jest nieprawidłowa.", + "storage-max-files-required":"Wymagana jest liczba.", + "storage-max-records":"Maksymalna liczba rekordów w magazynie", + "storage-max-records-min":"Minimalna liczba rekordów wynosi 1.", + "storage-max-records-pattern":"Liczba jest nieprawidłowa.", + "storage-max-records-required":"Wymagana jest maksymalna liczba rekordów.", + "storage-read-record-count":"Liczba odczytanych rekordów w magazynie", + "storage-read-record-count-min":"Minimalna liczba rekordów wynosi 1.", + "storage-read-record-count-pattern":"Liczba jest nieprawidłowa.", + "storage-read-record-count-required":"Wymagana jest liczba odczytanych rekordów.", + "storage-max-read-record-count":"Maksymalna liczba odczytanych rekordów w magazynie", + "storage-max-read-record-count-min":"Minimalna liczba rekordów wynosi 1.", + "storage-max-read-record-count-pattern":"Liczba jest nieprawidłowa.", + "storage-max-read-record-count-required":"Wymagana jest maksymalna liczba odczytanych rekordów.", + "storage-data-folder-path":"Ścieżka folderu danych", + "storage-data-folder-path-required":"Wymagana jest ścieżka folderu danych.", + "storage-pack-size":"Maksymalny rozmiar pakietu zdarzeń", + "storage-pack-size-min":"Minimalna liczba wynosi 1.", + "storage-pack-size-pattern":"Liczba jest nieprawidłowa.", + "storage-pack-size-required":"Wymagany jest maksymalny rozmiar pakietu zdarzeń.", + "storage-path":"Ścieżka magazynu", + "storage-path-required":"Wymagana jest ścieżka magazynu.", + "storage-type":"Typ magazynu", + "storage-types":{ + "file-storage":"Magazyn plików", + "memory-storage":"Magazyn pamięci", + "sqlite":"SQLite" + }, + "thingsboard":"ThingsBoard", + "general":"Ogólne", + "thingsboard-host":"Host ThingsBoard", + "thingsboard-host-required":"Host jest wymagany.", + "thingsboard-port":"Port ThingsBoard", + "thingsboard-port-max":"Maksymalny numer portu to 65535.", + "thingsboard-port-min":"Minimalny numer portu to 1.", + "thingsboard-port-pattern":"Port jest niepoprawny.", + "thingsboard-port-required":"Port jest wymagany.", + "tidy":"Porządkuj", + "tidy-tip":"Porządkuj konfigurację JSON", + "title-connectors-json":"Konfiguracja konektora {{typeName}}", + "tls-path-ca-certificate":"Ścieżka do certyfikatu CA na gatewayu", + "tls-path-client-certificate":"Ścieżka do certyfikatu klienta na gatewayu", + "messages-ttl-check-in-hours":"Sprawdź TTL wiadomości w godzinach", + "messages-ttl-check-in-hours-required":"Sprawdzenie TTL wiadomości w godzinach jest wymagane.", + "messages-ttl-check-in-hours-min":"Minimalna liczba to 1.", + "messages-ttl-check-in-hours-pattern":"Liczba jest niepoprawna.", + "messages-ttl-in-days":"TTL wiadomości w dniach", + "messages-ttl-in-days-required":"TTL wiadomości w dniach jest wymagane.", + "messages-ttl-in-days-min":"Minimalna liczba to 1.", + "messages-ttl-in-days-pattern":"Liczba jest niepoprawna.", + "mqtt-qos":"QoS", + "mqtt-qos-required":"QoS jest wymagane", + "mqtt-qos-range":"Wartości QoS są w zakresie od 0 do 1", + "tls-path-private-key":"Ścieżka do prywatnego klucza na gatewayu", + "toggle-fullscreen":"Przełącz pełny ekran", + "transformer-json-config":"Konfiguracja JSON*", + "update-config":"Dodaj/aktualizuj konfigurację JSON", + "hints":{ + "remote-configuration":"Umożliwia zdalną konfigurację i zarządzanie bramką", + "remote-shell":"Umożliwia zdalne sterowanie systemem operacyjnym bramki z widżetu Zdalnej Powłoki", + "host":"Nazwa hosta lub adres IP serwera ThingsBoard", + "port":"Port usługi MQTT na serwerze ThingsBoard", + "token":"Token dostępowy dla bramki na serwerze ThingsBoard", + "client-id":"Identyfikator klienta MQTT dla bramki na serwerze ThingsBoard", + "username":"Nazwa użytkownika MQTT dla bramki na serwerze ThingsBoard", + "password":"Hasło MQTT dla bramki na serwerze ThingsBoard", + "ca-cert":"Ścieżka do pliku certyfikatu CA", + "date-form":"Format daty w komunikatach dziennika", + "data-folder":"Ścieżka do folderu, który będzie zawierał dane (względna lub bezwzględna)", + "log-format":"Format komunikatu dziennika", + "remote-log":"Umożliwia zdalne logowanie i odczyt dziennika z bramki", + "backup-count":"Jeśli liczba kopii zapasowych > 0, po dokonaniu zmiany rolki, nie więcej niż określona liczba plików kopii zapasowych jest przechowywana - najstarsze zostaną usunięte", + "storage":"Zapewnia konfigurację zapisu przychodzących danych przed ich wysłaniem na platformę", + "max-file-count":"Maksymalna liczba plików, które zostaną utworzone", + "max-read-count":"Liczba wiadomości do pobrania z magazynu i wysłania do ThingsBoard", + "max-records":"Maksymalna liczba rekordów przechowywanych w jednym pliku", + "read-record-count":"Liczba wiadomości do pobrania z magazynu i wysłania do ThingsBoard", + "max-records-count":"Maksymalna liczba danych w magazynie przed wysłaniem do ThingsBoard", + "ttl-check-hour":"Jak często brama sprawdza dane pod kątem przestarzałości", + "ttl-messages-day":"Maksymalna liczba dni, przez które dane będą przechowywane w magazynie", + "commands":"Polecenia do zbierania dodatkowych statystyk", + "attribute":"Klucz telemetrii statystyk", + "timeout":"Limit czasu na wykonanie polecenia", + "command":"Wynik wykonania polecenia, będzie używany jako wartość telemetrii", + "check-device-activity":"Umożliwia monitorowanie aktywności każdego podłączonego urządzenia", + "inactivity-timeout":"Czas po którym brama rozłączy urządzenie", + "inactivity-period":"Okresowość sprawdzania aktywności urządzenia", + "minimal-pack-delay":"Opóźnienie między wysyłką paczek wiadomości (zmniejszenie tego ustawienia zwiększa zużycie procesora)", + "qos":"Jakość usługi w komunikacji MQTT (0 - przynajmniej raz, 1 - co najmniej raz)", + "server-port":"Port sieciowy, na którym serwer GRPC nasłuchuje nadchodzących połączeń.", + "grpc-keep-alive-timeout":"Maksymalny czas oczekiwania serwera na odpowiedź keepalive ping przed uznaniem połączenia za martwe.", + "grpc-keep-alive":"Czas między dwoma kolejnymi wiadomościami keepalive ping, gdy nie ma aktywnego wywołania RPC.", + "grpc-min-time-between-pings":"Minimalny czas oczekiwania serwera między wysłaniem wiadomości keepalive ping.", + "grpc-max-pings-without-data":"Maksymalna liczba wiadomości keepalive ping, które serwer może wysłać, nie odbierając żadnych danych, zanim uzna połączenie za martwe.", + "grpc-min-ping-interval-without-data":"Minimalny czas oczekiwania serwera między wysłaniem wiadomości keepalive ping, gdy nie są przesyłane ani odbierane żadne dane.", + "permit-without-calls":"Zezwalaj serwerowi na utrzymanie połączenia GRPC aktywnego, nawet gdy nie ma aktywnych wywołań RPC." + } + }, + "grid":{ + "delete-item-title":"Czy na pewno chcesz usunąć ten element?", + "delete-item-text":"Bądź ostrożny, po potwierdzeniu ten element i wszystkie powiązane dane staną się nieodwracalne.", + "delete-items-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 element} other {# elementów} }?", + "delete-items-action-title":"Usuń { count, plural, =1 {1 element} other {# elementów} }", + "delete-items-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane elementy zostaną usunięte, a wszystkie powiązane dane staną się nieodwracalne.", + "add-item-text":"Dodaj nowy element", + "no-items-text":"Nie znaleziono elementów", + "item-details":"Szczegóły elementu", + "delete-item":"Usuń element", + "delete-items":"Usuń elementy", + "scroll-to-top":"Przewiń do góry" + }, + "help":{ + "goto-help-page":"Przejdź do strony pomocy", + "show-help":"Pokaż pomoc" + }, + "home":{ + "home":"Strona główna", + "profile":"Profil", + "logout":"Wyloguj się", + "menu":"Menu", + "avatar":"Awatar", + "open-user-menu":"Otwórz menu użytkownika" + }, + "file-input":{ + "browse-file":"Przeglądaj plik", + "browse-files":"Przeglądaj pliki" + }, + "image":{ + "gallery":"Galeria obrazów", + "search":"Wyszukaj obraz", + "selected-images":"{ count, plural, =1 {1 obraz} other {# obrazy} } wybranych", + "created-time":"Czas utworzenia", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "resolution":"Rozdzielczość", + "size":"Rozmiar", + "system":"System", + "download-image":"Pobierz obraz", + "export-image":"Eksportuj obraz do JSON", + "import-image":"Importuj obraz z JSON", + "upload-image":"Prześlij obraz", + "edit-image":"Edytuj obraz", + "image-details":"Szczegóły obrazu", + "no-images":"Nie znaleziono obrazów", + "delete-image":"Usuń obraz", + "delete-image-title":"Czy na pewno chcesz usunąć obraz '{{imageTitle}}'?", + "delete-image-text":"Bądź ostrożny, po potwierdzeniu obraz stanie się nieodwracalnie utracony.", + "delete-images-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 obraz} other {# obrazy} }?", + "delete-images-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane obrazy zostaną usunięte, a wszelkie powiązane dane staną się nieodwracalnie utracone.", + "list-mode":"Widok listy", + "grid-mode":"Widok siatki", + "image-preview":"Podgląd obrazu", + "update-image":"Aktualizuj obraz", + "export-failed-error":"Nie można wyeksportować obrazu: {{error}}", + "image-json-file":"Plik JSON obrazu", + "invalid-image-json-file-error":"Nie można zaimportować obrazu z JSON: Nieprawidłowa struktura danych JSON obrazu.", + "image-is-in-use":"Obraz jest używany przez inne jednostki", + "images-are-in-use":"Obrazy są używane przez inne jednostki", + "image-is-in-use-text":"Obraz '{{title}}' nie został usunięty, ponieważ jest używany przez następujące jednostki:", + "images-are-in-use-text":"Nie wszystkie obrazy zostały usunięte, ponieważ są używane przez inne jednostki.
    Możesz zobaczyć odwołane jednostki, klikając przycisk Odwołania w odpowiednim wierszu obrazu.
    Jeśli nadal chcesz usunąć te obrazy, zaznacz je w tabeli poniżej i kliknij przycisk Usuń zaznaczone.", + "delete-image-in-use-text":"Jeśli nadal chcesz usunąć obraz, kliknij przycisk Usuń tak czy inaczej.", + "system-entities":"Jednostki systemowe:", + "entities":"jednostki:", + "references":"Odwołania", + "include-system-images":"Dołącz obrazy systemowe", + "clear-image":"Wyczyść obraz", + "no-image":"Brak obrazu", + "no-image-selected":"Nie wybrano obrazu", + "browse-from-gallery":"Przeglądaj z galerii", + "set-link":"Ustaw link", + "image-link":"Link obrazu", + "link":"Link", + "copy-image-link":"Kopiuj link obrazu", + "embed-image":"Osadź obraz", + "embed-to-html":"Osadź w HTML", + "embed-to-html-hint":"Ta funkcja umożliwia udostępnianie linku każdemu nieautoryzowanemu użytkownikowi.", + "embed-to-html-text":"Za pomocą poniższego fragmentu kodu możesz osadzić obraz w komponentach opartych na prostym HTML.
    Takie komponenty obejmują widżety karty HTML, funkcje zawartości komórek itp.", + "embed-to-angular-template":"Osadź w szablonie Angular HTML", + "embed-to-angular-template-text":"Za pomocą poniższego fragmentu kodu możesz osadzić obraz w szablonie Angular HTML.
    Takie komponenty obejmują widżet Markdown, sekcję HTML w edytorze widżetów, niestandardowe akcje itp." + }, + "image-input":{ + "drop-images-or":"Przeciągnij i upuść obrazy lub", + "drag-and-drop":"Przeciągnij i Upuść", + "or":"lub", + "browse":"Przeglądaj", + "no-images":"Brak wybranych obrazów", + "images":"obrazy" + }, + "import":{ + "no-file":"Nie wybrano pliku", + "drop-file":"Przeciągnij plik JSON lub kliknij, aby wybrać plik do przesłania.", + "drop-json-file-or":"Przeciągnij plik JSON lub", + "drop-file-csv":"Przeciągnij plik CSV lub kliknij, aby wybrać plik do przesłania.", + "drop-file-csv-or":"Przeciągnij plik CSV lub", + "column-value":"Wartość", + "column-title":"Tytuł", + "column-example":"Przykładowa wartość danych", + "column-key":"Atrybut/klucz telemetryczny", + "credentials":"Dane uwierzytelniające", + "csv-delimiter":"Separator CSV", + "csv-first-line-header":"Pierwsza linia zawiera nazwy kolumn", + "csv-update-data":"Aktualizuj atrybuty/telemetrię", + "details":"Szczegóły", + "import-csv-number-columns-error":"Plik powinien zawierać co najmniej dwie kolumny", + "import-csv-invalid-format-error":"Nieprawidłowy format pliku. Linia: '{{line}}'", + "column-type":{ + "name":"Nazwa", + "type":"Typ", + "label":"Etykieta", + "column-type":"Typ kolumny", + "client-attribute":"Atrybut klienta", + "shared-attribute":"Wspólny atrybut", + "server-attribute":"Atrybut serwera", + "timeseries":"Szereg czasowy", + "entity-field":"Pole encji", + "access-token":"Token dostępu", + "x509":"X.509", + "mqtt":{ + "client-id":"ID klienta MQTT", + "user-name":"Nazwa użytkownika MQTT", + "password":"Hasło MQTT" + }, + "lwm2m":{ + "client-endpoint":"Nazwa klienta LwM2M endpoint", + "security-config-mode":"Tryb konfiguracji bezpieczeństwa LwM2M", + "client-identity":"Tożsamość klienta LwM2M", + "client-key":"Klucz klienta LwM2M", + "client-cert":"Certyfikat publiczny klienta LwM2M", + "bootstrap-server-security-mode":"Tryb bezpieczeństwa serwera rozruchowego LwM2M", + "bootstrap-server-secret-key":"Tajny klucz serwera rozruchowego LwM2M", + "bootstrap-server-public-key-id":"Klucz publiczny lub identyfikator serwera rozruchowego LwM2M", + "lwm2m-server-security-mode":"Tryb bezpieczeństwa serwera LwM2M", + "lwm2m-server-secret-key":"Tajny klucz serwera LwM2M", + "lwm2m-server-public-key-id":"Klucz publiczny lub identyfikator serwera LwM2M" + }, + "snmp":{ + "host":"Host SNMP", + "port":"Port SNMP", + "version":"Wersja SNMP (v1, v2c lub v3)", + "community-string":"Ciąg społeczności SNMP" + }, + "isgateway":"Czy to jest Bramą", + "activity-time-from-gateway-device":"Czas aktywności urządzenia bramowego", + "description":"Opis", + "routing-key":"Klucz brzegowy", + "secret":"Sekret brzegowy" + }, + "stepper-text":{ + "select-file":"Wybierz plik", + "configuration":"Importuj konfigurację", + "column-type":"Wybierz typy kolumn", + "creat-entities":"Tworzenie nowych jednostek" + }, + "message":{ + "create-entities":"{{count}} nowych jednostek zostało pomyślnie utworzonych.", + "update-entities":"{{count}} jednostek zostało pomyślnie zaktualizowanych.", + "error-entities":"Wystąpił błąd podczas tworzenia {{count}} jednostek." + } + }, + "item":{ + "selected":"Wybrane" + }, + "js-func":{ + "no-return-error":"Funkcja musi zwrócić wartość!", + "return-type-mismatch":"Funkcja musi zwrócić wartość typu '{{type}}'!", + "tidy":"Porządek", + "mini":"Mini" + }, + "key-val":{ + "key":"Klucz", + "value":"Wartość", + "remove-entry":"Usuń wpis", + "add-entry":"Dodaj wpis", + "no-data":"Brak danych" + }, + "layout":{ + "layout":"Układ", + "layouts":"Układy", + "manage":"Zarządzaj układami", + "settings":"Ustawienia układu", + "color":"Kolor", + "main":"Główny", + "right":"Prawo", + "left":"Lewo", + "select":"Wybierz docelowy układ", + "percentage-width":"Szerokość procentowa (%)", + "fixed-width":"Szerokość stała (px)", + "left-width":"Szerokość lewej kolumny (%)", + "right-width":"Szerokość prawej kolumny (%)", + "pick-fixed-side":"Stała strona: ", + "layout-fixed-width":"Szerokość stała (px)", + "value-min-error":"Wartość musi być większa niż {{min}}{{unit}}", + "value-max-error":"Wartość musi być mniejsza niż {{max}}{{unit}}", + "layout-fixed-width-required":"Szerokość stała jest wymagana", + "right-width-percentage-required":"Wymagany jest procent szerokości prawej strony", + "left-width-percentage-required":"Wymagany jest procent szerokości lewej strony", + "divider":"Podziałka", + "right-side":"Układ prawej strony", + "left-side":"Układ lewej strony" + }, + "legend":{ + "direction":"Kierunek", + "position":"Pozycja", + "show-values":"Pokaż wartości", + "min-option":"Min", + "max-option":"Maks.", + "average-option":"Średnia", + "total-option":"Suma", + "latest-option":"Najnowsza", + "sort-legend":"Sortuj klucze danych w legendzie", + "show-max":"Pokaż maksymalną wartość", + "show-min":"Pokaż minimalną wartość", + "show-avg":"Pokaż średnią wartość", + "show-total":"Pokaż sumę wartości", + "show-latest":"Pokaż najnowszą wartość", + "settings":"Ustawienia legendy", + "min":"min", + "max":"maks.", + "avg":"śr.", + "total":"suma", + "latest":"najnowsza", + "comparison-time-ago":{ + "previousInterval":"(poprzedni przedział)", + "customInterval":"(niestandardowy przedział)", + "days":"(dzień temu)", + "weeks":"(tydzień temu)", + "months":"(miesiąc temu)", + "years":"(rok temu)" + }, + "label":"Etykieta", + "value":"Wartość" + }, + "login":{ + "login":"Zaloguj się", + "request-password-reset":"Zażądaj resetu hasła", + "reset-password":"Resetuj hasło", + "create-password":"Utwórz hasło", + "two-factor-authentication":"Autentykacja dwuskładnikowa", + "passwords-mismatch-error":"Wprowadzone hasła muszą być takie same!", + "password-again":"Powtórz hasło", + "sign-in":"Proszę się zalogować", + "username":"Nazwa użytkownika (e-mail)", + "remember-me":"Zapamiętaj mnie", + "forgot-password":"Zapomniałeś hasła?", + "password-reset":"Reset hasła", + "expired-password-reset-message":"Twoje dane uwierzytelniające wygasły! Proszę utworzyć nowe hasło.", + "new-password":"Nowe hasło", + "new-password-again":"Potwierdź nowe hasło", + "password-link-sent-message":"Link do resetowania hasła został wysłany", + "email":"E-mail", + "login-with":"Zaloguj się za pomocą {{name}}", + "or":"lub", + "error":"Błąd logowania", + "verify-your-identity":"Zweryfikuj swoją tożsamość", + "select-way-to-verify":"Wybierz sposób weryfikacji", + "resend-code":"Wyślij kod ponownie", + "resend-code-wait":"Ponownie wyślij kod za { time, plural, =1 {1 sekundę} other {# sekundy} }", + "try-another-way":"Spróbuj inną metodę", + "totp-auth-description":"Proszę podać kod z aplikacji autoryzacyjnej.", + "totp-auth-placeholder":"Kod", + "sms-auth-description":"Kod zabezpieczający został wysłany na Twój telefon pod numerem {{contact}}.", + "sms-auth-placeholder":"Kod SMS", + "email-auth-description":"Kod zabezpieczający został wysłany na Twój adres e-mailowy {{contact}}.", + "email-auth-placeholder":"Kod e-mailowy", + "backup-code-auth-description":"Proszę podać jeden z kodów zapasowych.", + "backup-code-auth-placeholder":"Kod zapasowy" + }, + "markdown":{ + "edit":"Edytuj", + "preview":"Podgląd", + "copy-code":"Kliknij, aby skopiować", + "copied":"Skopiowano!" + }, + "notification":{ + "action-button":"Przycisk akcji", + "action-type":"Typ akcji", + "active":"Aktywny", + "add-notification-recipients-group":"Dodaj grupę odbiorców powiadomień", + "add-notification-template":"Dodaj szablon powiadomienia", + "add-recipient":"Dodaj odbiorcę", + "add-recipients":"Dodaj odbiorców", + "add-rule":"Dodaj regułę", + "add-stage":"Dodaj etap", + "add-template":"Dodaj szablon", + "after":"Po", + "alarm-assignment-trigger-settings":"Ustawienia wyzwalacza przypisania alarmu", + "alarm-comment-trigger-settings":"Ustawienia wyzwalacza komentarza alarmu", + "alarm-trigger-settings":"Ustawienia wyzwalacza alarmu", + "all":"Wszystkie", + "api-feature-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich funkcji API", + "api-usage-trigger-settings":"Ustawienia wyzwalacza korzystania z API", + "new-platform-version-trigger-settings":"Ustawienia wyzwalacza nowej wersji platformy", + "rate-limits-trigger-settings":"Ustawienia wyzwalacza przekroczenia limitów szybkości", + "at-least-one-should-be-selected":"Przynajmniej jedna pozycja musi być zaznaczona", + "basic-settings":"Podstawowe ustawienia", + "button-text":"Tekst przycisku", + "button-text-required":"Tekst przycisku jest wymagany", + "button-text-max-length":"Tekst przycisku powinien być krótszy lub równy {{ length }} znaków", + "compose":"Komponuj", + "conversation":"Rozmowa", + "conversation-required":"Rozmowa jest wymagana", + "copy-notification-template":"Skopiuj szablon powiadomienia", + "copy-rule":"Skopiuj regułę", + "copy-template":"Skopiuj szablon", + "create-new":"Utwórz nowe", + "created":"Utworzono", + "delete-notification-text":"Uważaj, po potwierdzeniu powiadomienie stanie się nieodwracalne.", + "delete-notification-title":"Czy na pewno chcesz usunąć powiadomienie?", + "delete-notifications-text":"Uważaj, po potwierdzeniu powiadomienia staną się nieodwracalne.", + "delete-notifications-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 powiadomienie} other {# powiadomienia} }?", + "delete-recipient-text":"Uważaj, po potwierdzeniu odbiorca stanie się nieodwracalny.", + "delete-recipient-title":"Czy na pewno chcesz usunąć odbiorcę '{{recipientName}}'?", + "delete-recipients-text":"Uważaj, po potwierdzeniu odbiorcy staną się nieodwracalni.", + "delete-recipients-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 odbiorcę} other {# odbiorców} }?", + "delete-request-text":"Uważaj, po potwierdzeniu żądanie stanie się nieodwracalne.", + "delete-request-title":"Czy na pewno chcesz usunąć żądanie?", + "delete-requests-text":"Uważaj, po potwierdzeniu żądania staną się nieodwracalne.", + "delete-requests-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 żądanie} other {# żądań} }?", + "delete-rule-text":"Uważaj, po potwierdzeniu reguła stanie się nieodwracalna.", + "delete-rule-title":"Czy na pewno chcesz usunąć regułę '{{ruleName}}'?", + "delete-rules-text":"Uważaj, po potwierdzeniu reguły staną się nieodwracalne.", + "delete-rules-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 regułę} other {# reguł} }?", + "delete-template-text":"Uważaj, po potwierdzeniu szablon stanie się nieodwracalny.", + "delete-template-title":"Czy na pewno chcesz usunąć szablon '{{templateName}}'?", + "delete-templates-text":"Uważaj, po potwierdzeniu szablony staną się nieodwracalne.", + "delete-templates-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 szablon} other {# szablonów} }?", + "deleted":"Usunięto", + "delivery-method":{ + "delivery-method":"Metoda dostarczania", + "email":"Email", + "email-preview":"Podgląd powiadomienia email", + "slack":"Slack", + "slack-preview":"Podgląd powiadomienia Slack", + "microsoft-teams":"Microsoft Teams", + "microsoft-teams-preview":"Podgląd powiadomienia Microsoft Teams", + "sms":"SMS", + "sms-preview":"Podgląd powiadomienia SMS", + "web":"Web", + "web-preview":"Podgląd powiadomienia web" + }, + "delivery-method-not-configure-click":"Metoda dostarczania nie jest skonfigurowana. Kliknij, aby skonfigurować.", + "delivery-method-not-configure-contact":"Metoda dostarczania nie jest skonfigurowana. Skontaktuj się z administratorem systemu.", + "delivery-methods":"Metody dostarczania", + "description":"Opis", + "device-activity-trigger-settings":"Ustawienia wyzwalacza aktywności urządzenia", + "device-list-rule-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich urządzeń", + "device-profiles-list-rule-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich profili urządzeń", + "disabled":"Wyłączone", + "edit-notification-recipients-group":"Edytuj grupę odbiorców powiadomień", + "edit-notification-template":"Edytuj szablon powiadomień", + "edit-rule":"Edytuj regułę", + "edit-template":"Edytuj szablon", + "enabled":"Włączone", + "entities-limit-trigger-settings":"Ustawienia wyzwalacza limitu encji", + "entity-action-trigger-settings":"Ustawienia wyzwalacza akcji encji", + "entity-type":"Typ encji", + "escalation-chain":"Łańcuch eskalacji", + "failed-send":"Błędy wysyłki", + "fails":"{ count, plural, =1 {1 błąd} other {# błędów} }", + "filter":"Filtr", + "first-recipient":"Pierwszy odbiorca", + "inactive":"Nieaktywne", + "inbox":"Skrzynka odbiorcza", + "notification-inbox":"Powiadomienia / Skrzynka odbiorcza", + "input-field-support-templatization":"Pole wejściowe obsługuje szablonowanie.", + "input-fields-support-templatization":"Pola wejściowe obsługują szablonowanie.", + "link":"Odnośnik", + "link-required":"Odnośnik jest wymagany", + "link-type":{ + "dashboard":"Otwórz pulpit nawigacyjny", + "link":"Otwórz link URL" + }, + "loading-notifications":"Ładowanie powiadomień...", + "management":"Zarządzanie powiadomieniami", + "mark-all-as-read":"Oznacz wszystkie jako przeczytane", + "mark-as-read":"Oznacz jako przeczytane", + "message":"Wiadomość", + "message-required":"Wiadomość jest wymagana", + "message-max-length":"Wiadomość powinna mieć mniej niż {{ length }} znaków", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana", + "new-notification":"Nowe powiadomienie", + "no-inbox-notification":"Nie znaleziono powiadomień", + "no-notification-request":"Brak zapytania o powiadomienie", + "no-notification-templates":"Nie znaleziono szablonów powiadomień", + "no-notifications-yet":"Brak powiadomień", + "no-recipients-notification":"Powiadomienie bez odbiorców", + "no-rule":"Nie skonfigurowano reguły", + "no-rules-notification":"Brak powiadomienia o regułach", + "no-severity-found":"Nie znaleziono powagi", + "no-severity-matching":"Nie znaleziono '{{severity}}'.", + "no-template-matching":"Nie znaleziono zasobów pasujących do '{{template}}'.", + "not-found-slack-recipient":"Nie znaleziono odbiorcy Slack", + "notification":"Powiadomienie", + "notification-center":"Centrum powiadomień", + "notify":"powiadomić", + "notify-again":"Powiadom ponownie", + "notify-alarm-action":{ + "acknowledged":"Alarm potwierdzony", + "assigned":"Alarm przypisany", + "cleared":"Alarm wyczyszczony", + "created":"Alarm utworzony", + "severity-changed":"Zmieniono powagę alarmu", + "unassigned":"Alarm nieprzypisany" + }, + "notify-on":"Powiadamiaj o", + "notify-on-comment-update":"Powiadamiaj o aktualizacji komentarza", + "notify-on-required":"Wymagane jest powiadomienie o", + "notify-on-unassign":"Powiadamiaj o cofnięciu przypisania", + "notify-only-user-comments":"Powiadamiaj tylko o komentarzach użytkownika", + "only-rule-chain-lifecycle-failures":"Tylko niepowodzenia cyklu życia łańcucha reguł", + "only-rule-node-lifecycle-failures":"Tylko niepowodzenia cyklu życia węzła reguły", + "platform-users":"Użytkownicy platformy", + "rate-limits":"Limity częstotliwości", + "rate-limits-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich limitów częstotliwości", + "recipient":"Odbiorca", + "recipient-group":"Grupa odbiorców", + "recipient-type":{ + "affected-tenant-administrators":"Dotknięci administratorzy najemcy", + "affected-user":"Dotknięty użytkownik", + "all-users":"Wszyscy użytkownicy", + "customer-users":"Użytkownicy klientów", + "system-administrators":"Administratorzy systemu", + "tenant-administrators":"Administratorzy najemcy", + "user-filters":"Filtr użytkowników", + "user-list":"Lista użytkowników", + "users-entity-owner":"Użytkownicy właściciela jednostki" + }, + "recipients":"Odbiorcy", + "notification-recipients":"Powiadomienia / Odbiorcy", + "recipients-count":"{ count, plural, =1 {1 odbiorca} other {# odbiorców} }", + "recipients-required":"Odbiorcy są wymagani", + "refresh-allow-delivery-method":"Odśwież metodę dostarczania", + "request-search":"Wyszukaj żądanie", + "request-status":{ + "processing":"Przetwarzanie", + "scheduled":"Zaplanowane", + "sent":"Wysłane" + }, + "review":"Recenzja", + "rule":"Reguła", + "rule-chain-list-rule-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich łańcuchów reguł", + "rule-engine-events-trigger-settings":"Ustawienia wyzwalaczy zdarzeń silnika reguł", + "rule-engine-filter":"Filtr silnika reguł", + "rule-name":"Nazwa reguły", + "rule-name-required":"Nazwa jest wymagana", + "rule-disable":"Wyłącz regułę powiadomienia", + "rule-enable":"Włącz regułę powiadomienia", + "rule-node-filter":"Filtr węzła reguły", + "rules":"Reguły", + "notification-rules":"Powiadomienia / Reguły", + "scheduler-later":"Zaplanuj na później", + "search-notification":"Wyszukaj powiadomienia", + "search-recipients":"Wyszukaj odbiorców", + "search-rules":"Wyszukaj reguły", + "search-templates":"Wyszukaj szablony", + "see-documentation":"Zobacz dokumentację", + "selected-notifications":"{ count, plural, =1 {1 powiadomienie} other {# powiadomień} } wybrano", + "selected-recipients":"{ count, plural, =1 {1 odbiorca} other {# odbiorców} } wybrano", + "selected-requests":"{ count, plural, =1 {1 żądanie} other {# żądań} } wybrano", + "selected-rules":"{ count, plural, =1 {1 reguła} other {# reguł} } wybrano", + "selected-template":"{ count, plural, =1 {1 szablon} other {# szablonów} } wybrano", + "send-notification":"Wyślij powiadomienie", + "sent":"Wysłane", + "notification-sent":"Powiadomienia / Wysłane", + "set-entity-from-notification":"Ustaw jednostkę z powiadomienia na stan pulpitu nawigacyjnego", + "slack-chanel-type":"Typ kanału Slack", + "slack-chanel-types":{ + "direct":"Wiadomość bezpośrednia", + "private-channel":"Prywatny kanał", + "public-channel":"Publiczny kanał" + }, + "start-from-scratch":"Rozpocznij od początku", + "status":"Status", + "stop-escalation-alarm-status-become":"Zatrzymaj eskalację, gdy status alarmu stanie się:", + "subject":"Temat", + "subject-required":"Temat jest wymagany", + "template":"Szablon", + "template-name":"Nazwa szablonu", + "template-required":"Szablon jest wymagany", + "template-type":{ + "alarm":"Alarm", + "alarm-assignment":"Przypisanie alarmu", + "alarm-comment":"Komentarz do alarmu", + "api-usage-limit":"Limit użycia API", + "device-activity":"Aktywność urządzenia", + "entities-limit":"Limit jednostek", + "entity-action":"Akcja jednostki", + "general":"Ogólny", + "rule-engine-lifecycle-event":"Zdarzenie cyklu życia silnika reguł", + "rule-node":"Węzeł reguły", + "new-platform-version":"Nowa wersja platformy", + "rate-limits":"Przekroczono limity częstotliwości" + }, + "templates":"Szablony", + "notification-templates":"Powiadomienia / Szablony", + "tenant-profiles-list-rule-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich profili najemców", + "tenants-list-rule-hint":"Jeśli pole jest puste, wyzwalacz zostanie zastosowany do wszystkich najemców", + "threshold":"Próg", + "theme-color":"Kolor motywu", + "time":"Czas", + "track-rule-node-events":"Śledź zdarzenia węzła reguły", + "trigger":{ + "alarm":"Alarm", + "alarm-assignment":"Przypisanie alarmu", + "alarm-comment":"Komentarz do alarmu", + "api-usage-limit":"Limit użycia API", + "device-activity":"Aktywność urządzenia", + "entities-limit":"Limit jednostek", + "entity-action":"Akcja jednostki", + "rule-engine-lifecycle-event":"Zdarzenie cyklu życia silnika reguł", + "new-platform-version":"Nowa wersja platformy", + "rate-limits":"Przekroczono limity częstotliwości", + "trigger":"Wyzwalacz", + "trigger-required":"Wymagany jest wyzwalacz" + }, + "type":"Typ", + "unread":"Nieprzeczytane", + "updated":"Zaktualizowane", + "use-template":"Użyj szablonu", + "view-all":"Pokaż wszystko", + "warning":"Ostrzeżenie", + "webhook-url":"URL Webhooka", + "webhook-url-required":"URL Webhooka jest wymagany", + "channel-name":"Nazwa kanału", + "channel-name-required":"Nazwa kanału jest wymagana", + "settings":{ + "notification-settings":"Ustawienia powiadomień", + "reset-all":"Zresetuj wszystkie ustawienia", + "reset-all-title":"Czy na pewno chcesz zresetować formularz?", + "reset-all-text":"Po potwierdzeniu, formularz ustawień zostanie zresetowany do domyślnej wartości i zapisany.", + "type":"Typ", + "enable-all":"Włącz wszystkie", + "disable-all":"Wyłącz wszystkie", + "delivery-not-configured":"Metoda dostarczania nie jest skonfigurowana" + } + }, + "ota-update":{ + "add":"Dodaj pakiet", + "assign-firmware":"Przypisane oprogramowanie", + "assign-firmware-required":"Przypisane oprogramowanie jest wymagane", + "assign-software":"Przypisane oprogramowanie", + "assign-software-required":"Przypisane oprogramowanie jest wymagane", + "auto-generate-checksum":"Automatycznie generuj sumę kontrolną", + "checksum":"Suma kontrolna", + "checksum-hint":"Jeśli suma kontrolna jest pusta, zostanie wygenerowana automatycznie", + "checksum-algorithm":"Algorytm sumy kontrolnej", + "checksum-copied-message":"Suma kontrolna pakietu została skopiowana do schowka", + "change-firmware":"Zmiana oprogramowania może spowodować aktualizację { count, plural, =1 {1 urządzenia} other {# urządzeń} }.", + "change-software":"Zmiana oprogramowania może spowodować aktualizację { count, plural, =1 {1 urządzenia} other {# urządzeń} }.", + "chose-compatible-device-profile":"Wgrany pakiet będzie dostępny tylko dla urządzeń z wybranym profilem.", + "chose-firmware-distributed-device":"Wybierz oprogramowanie, które zostanie rozprowadzone na urządzenia", + "chose-software-distributed-device":"Wybierz oprogramowanie, które zostanie rozprowadzone na urządzenia", + "content-type":"Typ zawartości", + "copy-checksum":"Kopiuj sumę kontrolną", + "copy-direct-url":"Kopiuj bezpośredni adres URL", + "copyId":"Kopiuj identyfikator pakietu", + "copied":"Skopiowane!", + "delete":"Usuń pakiet", + "delete-ota-update-text":"Bądź ostrożny, po potwierdzeniu aktualizacja OTA stanie się nieodwracalna.", + "delete-ota-update-title":"Czy na pewno chcesz usunąć aktualizację OTA „{{title}}”?", + "delete-ota-updates-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane aktualizacje OTA zostaną usunięte.", + "delete-ota-updates-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 aktualizację OTA} other {# aktualizacji OTA} }?", + "description":"Opis", + "direct-url":"Bezpośredni adres URL", + "direct-url-copied-message":"Bezpośredni adres URL pakietu został skopiowany do schowka", + "direct-url-required":"Bezpośredni adres URL jest wymagany", + "download":"Pobierz pakiet", + "drop-file":"Przeciągnij i upuść plik pakietu lub kliknij, aby wybrać plik do przesłania.", + "drop-package-file-or":"Przeciągnij i upuść plik pakietu lub", + "file-name":"Nazwa pliku", + "file-size":"Rozmiar pliku", + "file-size-bytes":"Rozmiar pliku w bajtach", + "idCopiedMessage":"Identyfikator pakietu został skopiowany do schowka", + "no-firmware-matching":"Nie znaleziono kompatybilnych pakietów aktualizacji oprogramowania układowego pasujących do „{{entity}}”.", + "no-firmware-text":"Brak kompatybilnych pakietów aktualizacji oprogramowania układowego.", + "no-packages-text":"Nie znaleziono pakietów", + "no-software-matching":"Nie znaleziono kompatybilnych pakietów aktualizacji oprogramowania pasujących do „{{entity}}”.", + "no-software-text":"Brak kompatybilnych pakietów aktualizacji oprogramowania.", + "ota-update":"Aktualizacja OTA", + "ota-update-details":"Szczegóły aktualizacji OTA", + "ota-updates":"Aktualizacje OTA", + "package-file":"Plik pakietu", + "package-type":"Typ pakietu", + "packages-repository":"Repozytorium pakietów", + "search":"Szukaj pakietów", + "selected-package":"{ count, plural, =1 {1 pakiet} other {# pakietów} } wybrano", + "title":"Tytuł", + "title-required":"Tytuł jest wymagany.", + "title-max-length":"Tytuł powinien mieć mniej niż 256", + "types":{ + "firmware":"Oprogramowanie układowe", + "software":"Oprogramowanie" + }, + "upload-binary-file":"Prześlij plik binarny", + "use-external-url":"Użyj zewnętrznego adresu URL", + "version":"Wersja", + "version-required":"Wersja jest wymagana.", + "version-tag":"Tag wersji", + "version-tag-hint":"Niestandardowy tag powinien pasować do wersji pakietu zgłoszonej przez Twoje urządzenie.", + "version-max-length":"Wersja powinna mieć mniej niż 256", + "warning-after-save-no-edit":"Po przesłaniu pakietu nie będziesz mógł edytować tytułu, wersji, profilu urządzenia i typu pakietu." + }, + "position":{ + "top":"Góra", + "bottom":"Dół", + "left":"Lewo", + "right":"Prawo" + }, + "profile":{ + "profile":"Profil", + "last-login-time":"Ostatnie logowanie", + "change-password":"Zmień hasło", + "current-password":"Obecne hasło", + "copy-jwt-token":"Kopiuj token JWT", + "jwt-token":"Token JWT", + "token-valid-till":"Token jest ważny do", + "tokenCopiedSuccessMessage":"Token JWT został skopiowany do schowka", + "tokenCopiedWarnMessage":"Token JWT wygasł! Proszę odświeżyć stronę." + }, + "profiles":{ + "profiles":"Profile" + }, + "security":{ + "security":"Bezpieczeństwo", + "general-settings":"Ogólne ustawienia bezpieczeństwa", + "access-token":"Token dostępu", + "access-token-required":"Token dostępu jest wymagany", + "clientId":"ID klienta", + "clientId-required":"ID klienta jest wymagane", + "username":"Nazwa użytkownika", + "username-required":"Nazwa użytkownika jest wymagana", + "ca-cert":"Certyfikat CA", + "2fa":{ + "2fa":"Uwierzytelnianie dwuskładnikowe", + "2fa-description":"Uwierzytelnianie dwuskładnikowe chroni Twoje konto przed nieautoryzowanym dostępem. Wystarczy wprowadzić kod zabezpieczeń podczas logowania.", + "authenticate-with":"Możesz uwierzytelniać się za pomocą:", + "disable-2fa-provider-text":"Wyłączenie {{name}} spowoduje obniżenie poziomu bezpieczeństwa Twojego konta", + "disable-2fa-provider-title":"Czy na pewno chcesz wyłączyć {{name}}?", + "get-new-code":"Pobierz nowy kod", + "main-2fa-method":"Użyj jako główna metoda uwierzytelniania dwuskładnikowego", + "dialog":{ + "activation-step-description-email":"Następnym razem, gdy się zalogujesz, zostaniesz poproszony o wprowadzenie kodu zabezpieczeń, który zostanie wysłany na Twój adres e-mail.", + "activation-step-description-sms":"Następnym razem, gdy się zalogujesz, zostaniesz poproszony o wprowadzenie kodu zabezpieczeń, który zostanie wysłany na numer telefonu.", + "activation-step-description-totp":"Następnym razem, gdy się zalogujesz, będziesz musiał wprowadzić kod uwierzytelniania dwuskładnikowego.", + "activation-step-label":"Aktywacja", + "backup-code-description":"Wydrukuj kody, aby mieć je pod ręką, gdy będziesz musiał ich użyć do zalogowania się na swoje konto. Każdy kod zapasowy można użyć tylko raz.", + "backup-code-warn":"Po opuszczeniu tej strony te kody nie będą już dostępne. Przechowuj je bezpiecznie, korzystając z dostępnych opcji poniżej.", + "download-txt":"Pobierz (txt)", + "email-step-description":"Podaj adres e-mail, który będziesz używać jako autentykatora.", + "email-step-label":"E-mail", + "enable-email-title":"Włącz autentykator e-mail", + "enable-sms-title":"Włącz autentykator SMS", + "enable-totp-title":"Włącz autentykator aplikacji", + "enter-verification-code":"Wprowadź tutaj 6-cyfrowy kod", + "get-backup-code-title":"Pobierz kod zapasowy", + "next":"Dalej", + "scan-qr-code":"Zeskanuj ten kod QR za pomocą swojej aplikacji uwierzytelniającej", + "send-code":"Wyślij kod", + "sms-step-description":"Podaj numer telefonu, który będziesz używać jako autentykatora.", + "sms-step-label":"Numer telefonu", + "success":"Sukces!", + "totp-step-description-install":"Możesz zainstalować aplikacje takie jak Google Authenticator, Authy lub Duo.", + "totp-step-description-open":"Otwórz aplikację autentykatora na swoim telefonie komórkowym.", + "totp-step-label":"Pobierz aplikację", + "verification-code":"Kod weryfikacyjny 6-cyfrowy", + "verification-code-invalid":"Nieprawidłowy format kodu weryfikacyjnego", + "verification-code-incorrect":"Kod weryfikacyjny jest niepoprawny", + "verification-code-many-request":"Zbyt wiele żądań sprawdzenia kodu weryfikacyjnego", + "verification-step-description":"Wprowadź 6-cyfrowy kod, który właśnie wysłaliśmy na adres {{address}}", + "verification-step-label":"Weryfikacja" + }, + "provider":{ + "email":"E-mail", + "email-description":"Użyj kodu zabezpieczeń wysłanego na Twój adres e-mail do uwierzytelniania.", + "email-hint":"Kody uwierzytelniania są wysyłane przez e-mail na adres {{ info }}", + "sms":"SMS", + "sms-description":"Użyj telefonu do uwierzytelniania. Wyślemy Ci kod zabezpieczeń za pomocą wiadomości SMS podczas logowania.", + "sms-hint":"Kody uwierzytelniania są wysyłane jako wiadomości tekstowe na numer telefonu {{ info }}", + "totp":"Aplikacja autentykatora", + "totp-description":"Używaj aplikacji takich jak Google Authenticator, Authy lub Duo na swoim telefonie do uwierzytelniania. Wygeneruje ona kod zabezpieczeń do logowania.", + "totp-hint":"Aplikacja autentykatora jest skonfigurowana dla Twojego konta", + "backup_code":"Kod zapasowy", + "backup-code-description":"Te jednorazowe kody do wydruku pozwalają Ci się zalogować, gdy jesteś z dala od telefonu, na przykład podczas podróży.", + "backup-code-hint":"Obecnie aktywnych jest {{ info }} jednorazowych kodów" + } + }, + "password-requirement":{ + "at-least":"Przynajmniej:", + "character":"{ count, plural, =1 {1 znak} other {# znaków} }", + "digit":"{ count, plural, =1 {1 cyfra} other {# cyfr} }", + "incorrect-password-try-again":"Nieprawidłowe hasło. Spróbuj ponownie", + "lowercase-letter":"{ count, plural, =1 {1 mała litera} other {# małe litery} }", + "new-passwords-not-match":"Nowe hasło nie pasuje", + "password-should-not-contain-spaces":"Twoje hasło nie powinno zawierać spacji", + "password-not-meet-requirements":"Hasło nie spełnia wymagań", + "password-requirements":"Wymagania dotyczące hasła", + "password-should-difference":"Nowe hasło powinno różnić się od bieżącego", + "special-character":"{ count, plural, =1 {1 znak specjalny} other {# znaków specjalnych} }", + "uppercase-letter":"{ count, plural, =1 {1 wielka litera} other {# wielkie litery} }", + "at-most":"Maksymalnie:" + } + }, + "relation":{ + "relations":"Relacje", + "direction":"Kierunek", + "clear-relation-type":"Wyczyść typ relacji", + "search-direction":{ + "FROM":"Od", + "TO":"Do" + }, + "direction-type":{ + "FROM":"from", + "TO":"to" + }, + "from-relations":"Wychodzące relacje", + "to-relations":"Przychodzące relacje", + "selected-relations":"{ count, plural, =1 {1 relacja} other {# relacji} } wybrano", + "type":"Typ", + "to-entity-type":"Typ jednostki docelowej", + "to-entity-name":"Nazwa jednostki docelowej", + "from-entity-type":"Typ jednostki źródłowej", + "from-entity-name":"Nazwa jednostki źródłowej", + "to-entity":"Jednostka docelowa", + "from-entity":"Jednostka źródłowa", + "delete":"Usuń relację", + "relation-type":"Typ relacji", + "relation-type-required":"Wymagany jest typ relacji.", + "relation-type-max-length":"Typ relacji powinien mieć mniej niż 256 znaków", + "any-relation-type":"Dowolny typ", + "add":"Dodaj relację", + "edit":"Edytuj relację", + "delete-to-relation-title":"Czy na pewno chcesz usunąć relację do jednostki '{{entityName}}'?", + "delete-to-relation-text":"Bądź ostrożny, po potwierdzeniu jednostka '{{entityName}}' zostanie odłączona od bieżącej jednostki.", + "delete-to-relations-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 relacja} other {# relacje} }?", + "delete-to-relations-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane relacje zostaną usunięte, a odpowiednie jednostki zostaną odłączone od bieżącej jednostki.", + "delete-from-relation-title":"Czy na pewno chcesz usunąć relację z jednostki '{{entityName}}'?", + "delete-from-relation-text":"Bądź ostrożny, po potwierdzeniu bieżąca jednostka zostanie odłączona od jednostki '{{entityName}}'.", + "delete-from-relations-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 relacja} other {# relacje} }?", + "delete-from-relations-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane relacje zostaną usunięte, a bieżąca jednostka zostanie odłączona od odpowiednich jednostek.", + "remove-relation-filter":"Usuń filtr relacji", + "remove-filter":"Usuń filtr", + "add-relation-filter":"Dodaj filtr relacji", + "any-relation":"Dowolna relacja", + "relation-filters":"Filtry relacji", + "additional-info":"Dodatkowe informacje (JSON)", + "invalid-additional-info":"Nie można sparsować dodatkowych informacji json.", + "no-relations-text":"Nie znaleziono relacji" + }, + "resource":{ + "add":"Dodaj zasób", + "all-types":"Wszystkie", + "copyId":"Skopiuj identyfikator zasobu", + "delete":"Usuń zasób", + "delete-resource-text":"Bądź ostrożny, po potwierdzeniu zasób zostanie bezpowrotnie usunięty.", + "delete-resource-title":"Czy na pewno chcesz usunąć zasób '{{resourceTitle}}'?", + "delete-resources-action-title":"Usuń { count, plural, =1 {1 zasób} other {# zasobów} }", + "delete-resources-text":"Proszę zauważyć, że wybrane zasoby, nawet jeśli są używane w profilach urządzeń, zostaną usunięte.", + "delete-resources-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 zasób} other {# zasobów} }?", + "download":"Pobierz zasób", + "drop-file":"Upuść plik zasobu lub kliknij, aby wybrać plik do przesłania.", + "drop-resource-file-or":"Przeciągnij i upuść plik zasobu lub", + "empty":"Zasób jest pusty", + "file-name":"Nazwa pliku", + "idCopiedMessage":"Identyfikator zasobu został skopiowany do schowka", + "no-resource-matching":"Nie znaleziono zasobów pasujących do '{{widgetsBundle}}'.", + "no-resource-text":"Nie znaleziono zasobów", + "open-widgets-bundle":"Otwórz paczkę widżetów", + "resource":"Zasób", + "resource-file":"Plik zasobu", + "resource-files":"Pliki zasobów", + "resource-library-details":"Szczegóły zasobu", + "resource-type":"Typ zasobu", + "resources-library":"Biblioteka zasobów", + "search":"Szukaj zasobów", + "selected-resources":"{ count, plural, =1 {1 zasób} other {# zasobów} } wybrano", + "system":"System", + "title":"Tytuł", + "title-required":"Tytuł jest wymagany.", + "title-max-length":"Tytuł powinien mieć mniej niż 256 znaków", + "type":{ + "jks":"JKS", + "js-module":"Moduł JS", + "lwm2m-model":"Model LWM2M", + "pkcs-12":"PKCS #12" + } + }, + "rulechain":{ + "rulechain":"Łańcuch reguł", + "rulechain-events":"Zdarzenia łańcucha reguł", + "rulechains":"Łańcuchy reguł", + "root":"Korzeń", + "delete":"Usuń łańcuch reguł", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "name-max-length":"Nazwa powinna mieć mniej niż 256 znaków", + "description":"Opis", + "add":"Dodaj łańcuch reguł", + "set-root":"Ustaw łańcuch reguł jako korzeń", + "set-root-rulechain-title":"Jesteś pewien, że chcesz ustawić łańcuch reguł '{{ruleChainName}}' jako korzeń?", + "set-root-rulechain-text":"Po potwierdzeniu łańcuch reguł stanie się korzeniem i będzie obsługiwał wszystkie przychodzące wiadomości transportowe.", + "delete-rulechain-title":"Jesteś pewien, że chcesz usunąć łańcuch reguł '{{ruleChainName}}'?", + "delete-rulechain-text":"Bądź ostrożny, po potwierdzeniu łańcuch reguł i wszystkie powiązane dane staną się nieodwracalne.", + "delete-rulechains-title":"Jesteś pewien, że chcesz usunąć { count, plural, =1 {1 łańcuch reguł} other {# łańcuchów reguł} }?", + "delete-rulechains-action-title":"Usuń { count, plural, =1 {1 łańcuch reguł} other {# łańcuchów reguł} }", + "delete-rulechains-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane łańcuchy reguł zostaną usunięte, a wszystkie powiązane dane staną się nieodwracalne.", + "add-rulechain-text":"Dodaj nowy łańcuch reguł", + "no-rulechains-text":"Nie znaleziono łańcuchów reguł", + "rulechain-details":"Szczegóły łańcucha reguł", + "details":"Szczegóły", + "events":"Zdarzenia", + "system":"System", + "import":"Importuj łańcuch reguł", + "export":"Eksportuj łańcuch reguł", + "export-failed-error":"Nie można wyeksportować łańcucha reguł: {{error}}", + "create-new-rulechain":"Utwórz nowy łańcuch reguł", + "rulechain-file":"Plik łańcucha reguł", + "invalid-rulechain-file-error":"Nie można zaimportować łańcucha reguł: Nieprawidłowa struktura danych łańcucha reguł.", + "copyId":"Skopiuj identyfikator łańcucha reguł", + "idCopiedMessage":"Identyfikator łańcucha reguł został skopiowany do schowka", + "select-rulechain":"Wybierz łańcuch reguł", + "no-rulechains-matching":"Nie znaleziono łańcuchów reguł pasujących do '{{entity}}'.", + "rulechain-required":"Łańcuch reguł jest wymagany", + "management":"Zarządzanie regułami", + "debug-mode":"Tryb debugowania", + "search":"Wyszukaj łańcuchy reguł", + "selected-rulechains":"{ count, plural, =1 {1 łańcuch reguł} other {# łańcuchów reguł} } wybranych", + "open-rulechain":"Otwórz łańcuch reguł", + "edge-template-root":"Korzeń szablonu", + "assign-to-edge":"Przypisz do krawędzi", + "edge-rulechain":"Łańcuch reguł krawędzi", + "unassign-rulechain-from-edge-text":"Po potwierdzeniu łańcuch reguł zostanie odłączony i nie będzie dostępny dla krawędzi.", + "unassign-rulechains-from-edge-title":"Jesteś pewien, że chcesz odłączyć { count, plural, =1 {1 łańcuch reguł} other {# łańcuchów reguł} }?", + "unassign-rulechains-from-edge-text":"Po potwierdzeniu wszystkie wybrane łańcuchy reguł zostaną odłączone i nie będą dostępne dla krawędzi.", + "assign-rulechain-to-edge-title":"Przypisz łańcuch(y) reguł do krawędzi", + "assign-rulechain-to-edge-text":"Wybierz łańcuchy reguł do przypisania do krawędzi", + "set-edge-template-root-rulechain":"Ustaw łańcuch reguł jako korzeń szablonu krawędzi", + "set-edge-template-root-rulechain-title":"Jesteś pewien, że chcesz ustawić łańcuch reguł '{{ruleChainName}}' jako korzeń szablonu krawędzi?", + "set-edge-template-root-rulechain-text":"Po potwierdzeniu łańcuch reguł stanie się korzeniem szablonu krawędzi i będzie korzeniem łańcucha reguł dla nowo tworzonych krawędzi.", + "invalid-rulechain-type-error":"Nie można zaimportować łańcucha reguł: Nieprawidłowy typ łańcucha reguł. Oczekiwany typ to {{expectedRuleChainType}}.", + "set-auto-assign-to-edge":"Przypisz łańcuch reguł do krawędzi podczas tworzenia", + "set-auto-assign-to-edge-title":"Jesteś pewien, że chcesz przypisać łańcuch reguł krawędzi '{{ruleChainName}}' do krawędzi podczas tworzenia?", + "set-auto-assign-to-edge-text":"Po potwierdzeniu łańcuch reguł krawędzi zostanie automatycznie przypisany do krawędzi podczas tworzenia.", + "unset-auto-assign-to-edge":"Nie przypisuj łańcucha reguł do krawędzi podczas tworzenia", + "unset-auto-assign-to-edge-title":"Jesteś pewien, że nie chcesz przypisywać łańcucha reguł krawędzi '{{ruleChainName}}' do krawędzi podczas tworzenia?", + "unset-auto-assign-to-edge-text":"Po potwierdzeniu łańcuch reguł krawędzi nie będzie automatycznie przypisywany do krawędzi podczas tworzenia.", + "unassign-rulechain-title":"Jesteś pewien, że chcesz odłączyć łańcuch reguł '{{ruleChainName}}'?", + "unassign-rulechains":"Odłącz łańcuchy reguł" + }, + "rulenode":{ + "rule-node-events":"Zdarzenia węzła reguły", + "details":"Szczegóły", + "events":"Zdarzenia", + "search":"Wyszukaj węzły", + "open-node-library":"Otwórz bibliotekę węzłów", + "close-node-library":"Zamknij bibliotekę węzłów", + "add":"Dodaj węzeł reguły", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "name-max-length":"Nazwa powinna mieć mniej niż 256 znaków", + "type":"Typ", + "rule-node-description":"Opis węzła reguły", + "delete":"Usuń węzeł reguły", + "select-all-objects":"Zaznacz wszystkie węzły i połączenia", + "deselect-all-objects":"Odznacz wszystkie węzły i połączenia", + "delete-selected-objects":"Usuń wybrane węzły i połączenia", + "delete-selected":"Usuń zaznaczone", + "create-nested-rulechain":"Utwórz zagnieżdżony łańcuch reguł", + "select-all":"Zaznacz wszystkie", + "copy-selected":"Skopiuj zaznaczone", + "deselect-all":"Odznacz wszystkie", + "rulenode-details":"Szczegóły węzła reguły", + "debug-mode":"Tryb debugowania", + "singleton-mode":"Tryb pojedynczej instancji", + "configuration":"Konfiguracja", + "link":"Połączenie", + "link-details":"Szczegóły połączenia węzła reguły", + "add-link":"Dodaj połączenie", + "link-label":"Etykieta połączenia", + "link-label-required":"Etykieta połączenia jest wymagana.", + "custom-link-label":"Niestandardowa etykieta połączenia", + "custom-link-label-required":"Niestandardowa etykieta połączenia jest wymagana.", + "link-labels":"Etykiety połączeń", + "link-labels-required":"Wymagane są etykiety połączeń.", + "no-link-labels-found":"Nie znaleziono etykiet połączeń", + "no-link-label-matching":"'{{label}}' nie zostało znalezione.", + "create-new-link-label":"Utwórz nową!", + "type-filter":"Filtr", + "type-filter-details":"Filtruj przychodzące wiadomości z określonymi warunkami", + "type-enrichment":"Wzbogacenie", + "type-enrichment-details":"Dodaj dodatkowe informacje do metadanych wiadomości", + "type-transformation":"Transformacja", + "type-transformation-details":"Zmień ładunek wiadomości i metadane", + "type-action":"Akcja", + "type-action-details":"Wykonaj specjalną akcję", + "type-external":"Zewnętrzne", + "type-external-details":"Komunikuje się z zewnętrznym systemem", + "type-rule-chain":"Łańcuch reguł", + "type-rule-chain-details":"Przekazuje przychodzące wiadomości do określonego łańcucha reguł", + "type-flow":"Przepływ", + "type-flow-details":"Organizuje przepływ wiadomości", + "type-input":"Wejście", + "type-input-details":"Logiczne wejście łańcucha reguł, przekazuje przychodzące wiadomości do następnego powiązanego węzła reguły", + "type-unknown":"Nieznany", + "type-unknown-details":"Nierozwiązany węzeł reguły", + "directive-is-not-loaded":"Zdefiniowana dyrektywa konfiguracji '{{directiveName}}' nie jest dostępna.", + "ui-resources-load-error":"Nie udało się załadować zasobów interfejsu użytkownika konfiguracji.", + "invalid-target-rulechain":"Nie można rozpoznać docelowego łańcucha reguł!", + "test-script-function":"Funkcja skryptu testowego", + "script-lang-java-script":"JavaScript", + "script-lang-tbel":"TBEL", + "message":"Wiadomość", + "message-type":"Typ wiadomości", + "select-message-type":"Wybierz typ wiadomości", + "message-type-required":"Wymagany jest typ wiadomości", + "metadata":"Metadane", + "metadata-required":"Wpisy metadanych nie mogą być puste.", + "output":"Wynik", + "test":"Test", + "help":"Pomoc", + "reset-debug-mode":"Zresetuj tryb debugowania we wszystkich węzłach", + "test-with-this-message":"{{test}} z tą wiadomością", + "queue-hint":"Wybierz kolejkę do przekazywania wiadomości do innej kolejki. Domyślnie używana jest kolejka „Main”.", + "queue-singleton-hint":"Wybierz kolejkę do przekazywania wiadomości w środowiskach wielu instancji. Domyślnie używana jest kolejka „Main”." + }, + "timezone":{ + "timezone":"Strefa czasowa", + "select-timezone":"Wybierz strefę czasową", + "no-timezones-matching":"Nie znaleziono stref czasowych pasujących do '{{timezone}}'.", + "timezone-required":"Strefa czasowa jest wymagana.", + "browser-time":"Czas przeglądarki" + }, + "queue":{ + "queue-name":"Kolejka", + "no-queues-found":"Nie znaleziono kolejek.", + "no-queues-matching":"Nie znaleziono kolejek pasujących do '{{queue}}'.", + "select-name":"Wybierz nazwę kolejki", + "name":"Nazwa", + "name-required":"Nazwa kolejki jest wymagana!", + "name-unique":"Nazwa kolejki nie jest unikalna!", + "name-pattern":"Nazwa kolejki zawiera znak inny niż alfanumeryczny ASCII, '.', '_', i '-'!", + "queue-required":"Kolejka jest wymagana!", + "topic-required":"Temat kolejki jest wymagany!", + "poll-interval-required":"Interwał odpytywania jest wymagany!", + "poll-interval-min-value":"Wartość interwału odpytywania nie może być mniejsza niż 1", + "partitions-required":"Partycje są wymagane!", + "partitions-min-value":"Wartość partycji nie może być mniejsza niż 1", + "pack-processing-timeout-required":"Limit czasu przetwarzania jest wymagany", + "pack-processing-timeout-min-value":"Wartość limitu czasu przetwarzania nie może być mniejsza niż 1", + "batch-size-required":"Rozmiar partii jest wymagany!", + "batch-size-min-value":"Wartość rozmiaru partii nie może być mniejsza niż 1", + "retries-required":"Liczba prób jest wymagana!", + "retries-min-value":"Wartość liczby prób nie może być ujemna", + "failure-percentage-required":"Procent awaryjnych wiadomości jest wymagany!", + "failure-percentage-min-value":"Wartość procenta awaryjnych wiadomości nie może być mniejsza niż 0", + "failure-percentage-max-value":"Wartość procenta awaryjnych wiadomości nie może być większa niż 100", + "pause-between-retries-required":"Odstęp między próbami jest wymagany!", + "pause-between-retries-min-value":"Wartość odstępu między próbami nie może być mniejsza niż 1", + "max-pause-between-retries-required":"Maksymalny odstęp między próbami jest wymagany!", + "max-pause-between-retries-min-value":"Wartość maksymalnego odstępu między próbami nie może być mniejsza niż 1", + "submit-strategy-type-required":"Typ strategii przesyłania jest wymagany!", + "processing-strategy-type-required":"Typ strategii przetwarzania jest wymagany!", + "queues":"Kolejki", + "selected-queues":"{ count, plural, =1 {1 kolejka} other {# kolejek} } wybranych", + "delete-queue-title":"Czy na pewno chcesz usunąć kolejkę '{{queueName}}'?", + "delete-queues-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 kolejkę} other {# kolejki} }?", + "delete-queue-text":"Bądź ostrożny, po potwierdzeniu kolejka i wszystkie związane z nią dane zostaną bezpowrotnie usunięte.", + "delete-queues-text":"Po potwierdzeniu wszystkie wybrane kolejki zostaną usunięte i nie będą dostępne.", + "search":"Wyszukaj kolejkę", + "add":"Dodaj kolejkę", + "details":"Szczegóły kolejki", + "topic":"Temat", + "submit-settings":"Ustawienia przesyłania", + "submit-strategy":"Typ strategii *", + "grouping-parameter":"Parametr grupowania", + "processing-settings":"Ustawienia przetwarzania", + "processing-strategy":"Typ przetwarzania *", + "retries-settings":"Ustawienia prób", + "polling-settings":"Ustawienia odpytywania", + "batch-processing":"Przetwarzanie partiami", + "poll-interval":"Interwał odpytywania", + "partitions":"Partycje", + "immediate-processing":"Natychmiastowe przetwarzanie", + "consumer-per-partition":"Wysyłaj odpytywanie dla każdego konsumenta", + "consumer-per-partition-hint":"Włącz oddzielnych konsumentów dla każdej partycji", + "processing-timeout":"Przetwarzaj w ciągu, ms", + "batch-size":"Rozmiar partii", + "retries":"Liczba prób (0 - nieograniczona)", + "failure-percentage":"Procent awaryjnych wiadomości do pominięcia prób", + "pause-between-retries":"Odstęp między próbami, s", + "max-pause-between-retries":"Maksymalny odstęp między próbami, s", + "delete":"Usuń kolejkę", + "copyId":"Skopiuj Id kolejki", + "idCopiedMessage":"Id kolejki zostało skopiowane do schowka", + "description":"Opis", + "description-hint":"Ten tekst będzie wyświetlany w opisie kolejki zamiast wybranej strategii", + "alt-description":"Strategia przesyłania: {{submitStrategy}}, Strategia przetwarzania: {{processingStrategy}}", + "custom-properties":"Własne właściwości", + "custom-properties-hint":"Własne właściwości tworzenia kolejki (tematu), np. 'retention.ms:604800000;retention.bytes:1048576000'", + "strategies":{ + "sequential-by-originator-label":"Sekwencyjne według nadawcy", + "sequential-by-originator-hint":"Nowa wiadomość, na przykład dla urządzenia A, nie jest przesyłana, dopóki poprzednia wiadomość dla urządzenia A nie zostanie potwierdzona", + "sequential-by-tenant-label":"Sekwencyjne według najemcy", + "sequential-by-tenant-hint":"Nowa wiadomość, na przykład dla najemcy A, nie jest przesyłana, dopóki poprzednia wiadomość dla najemcy A nie zostanie potwierdzona", + "sequential-label":"Sekwencyjne", + "sequential-hint":"Nowa wiadomość nie jest przesyłana, dopóki poprzednia wiadomość nie zostanie potwierdzona", + "burst-label":"Burst", + "burst-hint":"Wszystkie wiadomości są przesyłane do łańcuchów reguł w kolejności ich przyjścia", + "batch-label":"Partia", + "batch-hint":"Nowa partia nie jest przesyłana, dopóki poprzednia partia nie zostanie potwierdzona", + "skip-all-failures-label":"Pomiń wszystkie awarie", + "skip-all-failures-hint":"Ignoruj wszystkie awarie", + "skip-all-failures-and-timeouts-label":"Pomiń wszystkie awarie i przerwy czasowe", + "skip-all-failures-and-timeouts-hint":"Ignoruj wszystkie awarie i przerwy czasowe", + "retry-all-label":"Ponów wszystkie", + "retry-all-hint":"Ponów wszystkie wiadomości z pakietu przetwarzania", + "retry-failed-label":"Ponów nieudane", + "retry-failed-hint":"Ponów wszystkie nieudane wiadomości z pakietu przetwarzania", + "retry-timeout-label":"Ponów przerwę czasową", + "retry-timeout-hint":"Ponów wszystkie wiadomości przekroczone czasem z pakietu przetwarzania", + "retry-failed-and-timeout-label":"Ponów nieudane i przerwę czasową", + "retry-failed-and-timeout-hint":"Ponów wszystkie nieudane i przekroczone czasem wiadomości z pakietu przetwarzania" + } + }, + "server-error":{ + "general":"Ogólny błąd serwera", + "authentication":"Błąd uwierzytelniania", + "jwt-token-expired":"Token JWT wygasł", + "tenant-trial-expired":"Próba najemcy wygasła", + "credentials-expired":"Wygasły poświadczenia", + "permission-denied":"Brak uprawnień", + "invalid-arguments":"Nieprawidłowe argumenty", + "bad-request-params":"Nieprawidłowe parametry żądania", + "item-not-found":"Nie znaleziono elementu", + "too-many-requests":"Zbyt wiele żądań", + "too-many-updates":"Zbyt wiele aktualizacji" + }, + "tenant":{ + "tenant":"Najemca", + "tenants":"Najemcy", + "management":"Zarządzanie najemcami", + "add":"Dodaj najemcę", + "admins":"Administratorzy", + "manage-tenant-admins":"Zarządzaj administratorami najemcy", + "delete":"Usuń najemcę", + "add-tenant-text":"Dodaj nowego najemcę", + "no-tenants-text":"Nie znaleziono najemców", + "tenant-details":"Szczegóły najemcy", + "title-max-length":"Tytuł powinien zawierać mniej niż 256 znaków", + "delete-tenant-title":"Czy na pewno chcesz usunąć najemcę '{{tenantTitle}}'?", + "delete-tenant-text":"Bądź ostrożny, po potwierdzeniu najemca i wszystkie związane z nim dane zostaną bezpowrotnie usunięte.", + "delete-tenants-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 najemcę} other {# najemców} }?", + "delete-tenants-action-title":"Usuń { count, plural, =1 {1 najemcę} other {# najemców} }", + "delete-tenants-text":"Bądź ostrożny, po potwierdzeniu wszyscy wybrani najemcy zostaną usunięci, a wszystkie związane z nimi dane zostaną bezpowrotnie usunięte.", + "title":"Tytuł", + "title-required":"Tytuł jest wymagany.", + "description":"Opis", + "details":"Szczegóły", + "events":"Zdarzenia", + "copyId":"Kopiuj Id najemcy", + "idCopiedMessage":"Id najemcy zostało skopiowane do schowka", + "select-tenant":"Wybierz najemcę", + "no-tenants-matching":"Nie znaleziono najemców pasujących do '{{entity}}'", + "tenant-required":"Najemca jest wymagany", + "search":"Szukaj najemców", + "selected-tenants":"{ count, plural, =1 {1 najemca} other {# najemcy} } wybrano", + "isolated-tb-rule-engine":"Użyj izolowanych kolejek ThingsBoard Rule Engine", + "isolated-tb-rule-engine-details":"Każdy najemca będzie miał dedykowane kolejki Rule Engine" + }, + "tenant-profile":{ + "tenant-profile":"Profil najemcy", + "tenant-profiles":"Profile najemców", + "add":"Dodaj profil najemcy", + "add-profile":"Dodaj profil", + "edit":"Edytuj profil najemcy", + "tenant-profile-details":"Szczegóły profilu najemcy", + "no-tenant-profiles-text":"Nie znaleziono profili najemców", + "name-max-length":"Nazwa powinna zawierać mniej niż 256 znaków", + "search":"Szukaj profili najemców", + "selected-tenant-profiles":"{ count, plural, =1 {1 profil najemcy} other {# profile najemców} } wybrano", + "no-tenant-profiles-matching":"Nie znaleziono profili najemców pasujących do '{{entity}}'", + "tenant-profile-required":"Profil najemcy jest wymagany", + "idCopiedMessage":"Id profilu najemcy zostało skopiowane do schowka", + "set-default":"Ustaw profil najemcy jako domyślny", + "delete":"Usuń profil najemcy", + "copyId":"Kopiuj Id profilu najemcy", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "data":"Dane profilu", + "profile-configuration":"Konfiguracja profilu", + "description":"Opis", + "default":"Domyślny", + "delete-tenant-profile-title":"Czy na pewno chcesz usunąć profil najemcy '{{tenantProfileName}}'?", + "delete-tenant-profile-text":"Bądź ostrożny, po potwierdzeniu profil najemcy i wszystkie związane z nim dane zostaną bezpowrotnie usunięte.", + "delete-tenant-profiles-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 profil najemcy} other {# profile najemców} }?", + "delete-tenant-profiles-text":"Bądź ostrożny, po potwierdzeniu wszystkie wybrane profile najemców zostaną usunięte, a wszystkie związane z nimi dane zostaną bezpowrotnie usunięte.", + "set-default-tenant-profile-title":"Czy na pewno chcesz ustawić profil najemcy '{{tenantProfileName}}' jako domyślny?", + "set-default-tenant-profile-text":"Po potwierdzeniu profil najemcy zostanie oznaczony jako domyślny i będzie używany dla nowych najemców bez określonego profilu.", + "no-tenant-profiles-found":"Nie znaleziono profili najemców.", + "create-new-tenant-profile":"Utwórz nowy!", + "create-tenant-profile":"Utwórz nowy profil najemcy", + "import":"Importuj profil najemcy", + "export":"Eksportuj profil najemcy", + "export-failed-error":"Nie można wyeksportować profilu najemcy: {{error}}", + "tenant-profile-file":"Plik profilu najemcy", + "invalid-tenant-profile-file-error":"Nie można zaimportować profilu najemcy: Nieprawidłowa struktura danych profilu najemcy.", + "advanced-settings":"Ustawienia zaawansowane", + "entities":"Encje", + "rule-engine":"Silnik reguł", + "time-to-live":"Czas życia", + "alarms-and-notifications":"Alarmy i powiadomienia", + "ota-files-in-bytes":"Pliki", + "ws-title":"WS", + "unlimited":"(0 - nielimitowany)", + "maximum-devices":"Maksymalna liczba urządzeń", + "maximum-devices-required":"Maksymalna liczba urządzeń jest wymagana.", + "maximum-devices-range":"Maksymalna liczba urządzeń nie może być ujemna", + "maximum-assets":"Maksymalna liczba zasobów", + "maximum-assets-required":"Maksymalna liczba zasobów jest wymagana.", + "maximum-assets-range":"Maksymalna liczba zasobów nie może być ujemna", + "maximum-customers":"Maksymalna liczba klientów", + "maximum-customers-required":"Maksymalna liczba klientów jest wymagana.", + "maximum-customers-range":"Maksymalna liczba klientów nie może być ujemna", + "maximum-users":"Maksymalna liczba użytkowników", + "maximum-users-required":"Maksymalna liczba użytkowników jest wymagana.", + "maximum-users-range":"Maksymalna liczba użytkowników nie może być ujemna", + "maximum-dashboards":"Maksymalna liczba desek rozdzielczych", + "maximum-dashboards-required":"Maksymalna liczba desek rozdzielczych jest wymagana.", + "maximum-dashboards-range":"Maksymalna liczba desek rozdzielczych nie może być ujemna", + "maximum-rule-chains":"Maksymalna liczba łańcuchów reguł", + "maximum-rule-chains-required":"Maksymalna liczba łańcuchów reguł jest wymagana.", + "maximum-rule-chains-range":"Maksymalna liczba łańcuchów reguł nie może być ujemna", + "maximum-resources-sum-data-size":"Maksymalny łączny rozmiar plików zasobów (bajty)", + "maximum-resources-sum-data-size-required":"Maksymalny łączny rozmiar plików zasobów jest wymagany.", + "maximum-resources-sum-data-size-range":"Maksymalny łączny rozmiar plików zasobów nie może być ujemny", + "maximum-resource-size":"Maksymalny rozmiar pliku zasobu (bajty)", + "maximum-resource-size-required":"Maksymalny rozmiar pliku zasobu jest wymagany", + "maximum-resource-size-range":"Maksymalny rozmiar pliku zasobu nie może być ujemny", + "maximum-ota-packages-sum-data-size":"Maksymalny łączny rozmiar plików pakietów OTA (bajty)", + "maximum-ota-package-sum-data-size-required":"Maksymalny łączny rozmiar plików pakietów OTA jest wymagany.", + "maximum-ota-package-sum-data-size-range":"Maksymalny łączny rozmiar plików pakietów OTA nie może być ujemny", + "rest-requests-for-tenant":"Zapytania REST dla najemcy", + "transport-tenant-telemetry-msg-rate-limit":"Limit wiadomości telemetrii najemcy", + "transport-tenant-telemetry-data-points-rate-limit":"Limit punktów danych telemetrii najemcy", + "transport-device-msg-rate-limit":"Limit wiadomości urządzenia", + "transport-device-telemetry-msg-rate-limit":"Limit wiadomości telemetrii urządzenia", + "transport-device-telemetry-data-points-rate-limit":"Limit punktów danych telemetrii urządzenia", + "tenant-entity-export-rate-limit":"Limit eksportu wersji encji", + "tenant-entity-import-rate-limit":"Limit ładowania wersji encji", + "tenant-notification-request-rate-limit":"Limit żądań powiadomień", + "tenant-notification-requests-per-rule-rate-limit":"Limit żądań powiadomień na regułę powiadomień", + "max-transport-messages":"Maksymalna liczba wiadomości transportowych", + "max-transport-messages-required":"Maksymalna liczba wiadomości transportowych jest wymagana.", + "max-transport-messages-range":"Maksymalna liczba wiadomości transportowych nie może być ujemna", + "max-transport-data-points":"Maksymalna liczba punktów danych transportowych", + "max-transport-data-points-required":"Maksymalna liczba punktów danych transportowych jest wymagana.", + "max-transport-data-points-range":"Maksymalna liczba punktów danych transportowych nie może być ujemna", + "max-r-e-executions":"Maksymalna liczba wykonanych operacji Silnika Reguł", + "max-r-e-executions-required":"Maksymalna liczba wykonanych operacji Silnika Reguł jest wymagana.", + "max-r-e-executions-range":"Maksymalna liczba wykonanych operacji Silnika Reguł nie może być ujemna", + "max-j-s-executions":"Maksymalna liczba wykonanych operacji JavaScript", + "max-j-s-executions-required":"Maksymalna liczba wykonanych operacji JavaScript jest wymagana.", + "max-j-s-executions-range":"Maksymalna liczba wykonanych operacji JavaScript nie może być ujemna", + "max-tbel-executions":"Maksymalna liczba wykonanych operacji TBEL", + "max-tbel-executions-required":"Maksymalna liczba wykonanych operacji TBEL jest wymagana.", + "max-tbel-executions-range":"Maksymalna liczba wykonanych operacji TBEL nie może być ujemna", + "max-d-p-storage-days":"Maksymalna liczba dni przechowywania punktów danych", + "max-d-p-storage-days-required":"Maksymalna liczba dni przechowywania punktów danych jest wymagana.", + "max-d-p-storage-days-range":"Maksymalna liczba dni przechowywania punktów danych nie może być ujemna", + "default-storage-ttl-days":"Domyślny czas życia pamięci masowej w dniach", + "default-storage-ttl-days-required":"Domyślny czas życia pamięci masowej w dniach jest wymagany.", + "default-storage-ttl-days-range":"Domyślny czas życia pamięci masowej w dniach nie może być ujemny", + "alarms-ttl-days":"Czas życia alarmów w dniach", + "alarms-ttl-days-required":"Czas życia alarmów jest wymagany", + "alarms-ttl-days-days-range":"Okres przechowywania alarmów (TTL) nie może być ujemny", + "rpc-ttl-days":"RPC Okres przechowywania (TTL) dni", + "rpc-ttl-days-required":"Wymagany okres przechowywania (TTL) dni dla RPC", + "rpc-ttl-days-days-range":"Okres przechowywania (TTL) dni dla RPC nie może być ujemny", + "queue-stats-ttl-days":"Okres przechowywania (TTL) dni statystyk kolejki", + "queue-stats-ttl-days-required":"Wymagany okres przechowywania (TTL) dni dla statystyk kolejki", + "queue-stats-ttl-days-range":"Okres przechowywania (TTL) dni dla statystyk kolejki nie może być ujemny", + "rule-engine-exceptions-ttl-days":"Okres przechowywania (TTL) dni wyjątków silnika reguł", + "rule-engine-exceptions-ttl-days-required":"Wymagany okres przechowywania (TTL) dni dla wyjątków silnika reguł", + "rule-engine-exceptions-ttl-days-range":"Okres przechowywania (TTL) dni dla wyjątków silnika reguł nie może być ujemny", + "max-rule-node-executions-per-message":"Maksymalna liczba wykonanych węzłów reguł na wiadomość", + "max-rule-node-executions-per-message-required":"Wymagana maksymalna liczba wykonanych węzłów reguł na wiadomość", + "max-rule-node-executions-per-message-range":"Maksymalna liczba wykonanych węzłów reguł na wiadomość nie może być ujemna", + "max-emails":"Maksymalna liczba wysłanych wiadomości e-mail", + "max-emails-required":"Wymagana maksymalna liczba wysłanych wiadomości e-mail", + "max-emails-range":"Maksymalna liczba wysłanych wiadomości e-mail nie może być ujemna", + "sms-enabled":"SMS włączony", + "max-sms":"Maksymalna liczba wysłanych wiadomości SMS", + "max-sms-required":"Wymagana maksymalna liczba wysłanych wiadomości SMS", + "max-sms-range":"Maksymalna liczba wysłanych wiadomości SMS nie może być ujemna", + "max-created-alarms":"Maksymalna liczba utworzonych alarmów", + "max-created-alarms-required":"Wymagana maksymalna liczba utworzonych alarmów", + "max-created-alarms-range":"Maksymalna liczba utworzonych alarmów nie może być ujemna", + "no-queue":"Brak skonfigurowanej kolejki", + "add-queue":"Dodaj kolejkę", + "queues-with-count":"Kolejki ({{count}})", + "tenant-rest-limits":"Żądania REST dla najemcy", + "customer-rest-limits":"Żądania REST dla klienta", + "incorrect-pattern-for-rate-limits":"Nieprawidłowy format dla limitów szybkości. Pary pojemność:okres (w sekundach) oddzielone przecinkiem, np. 100:1,2000:60", + "too-small-value-zero":"Wartość musi być większa niż 0", + "too-small-value-one":"Wartość musi być większa niż 1", + "queue-size-is-limited-by-system-configuration":"Rozmiar kolejki jest również ograniczony konfiguracją systemu.", + "cassandra-tenant-limits-configuration":"Zapytanie do Cassandry dla najemcy", + "ws-limit-max-sessions-per-tenant":"Maksymalna liczba sesji na najemcę", + "ws-limit-max-sessions-per-customer":"Maksymalna liczba sesji na klienta", + "ws-limit-max-sessions-per-regular-user":"Maksymalna liczba sesji na zwykłego użytkownika", + "ws-limit-max-sessions-per-public-user":"Maksymalna liczba sesji na publicznego użytkownika", + "ws-limit-queue-per-session":"Maksymalny rozmiar kolejki wiadomości na sesję", + "ws-limit-max-subscriptions-per-tenant":"Maksymalna liczba subskrypcji na najemcę", + "ws-limit-max-subscriptions-per-customer":"Maksymalna liczba subskrypcji na klienta", + "ws-limit-max-subscriptions-per-regular-user":"Maksymalna liczba subskrypcji na zwykłego użytkownika", + "ws-limit-max-subscriptions-per-public-user":"Maksymalna liczba subskrypcji na publicznego użytkownika", + "ws-limit-updates-per-session":"Aktualizacje WS na sesję", + "rate-limits":{ + "add-limit":"Dodaj limit", + "advanced-settings":"Zaawansowane ustawienia", + "edit-limit":"Edytuj limit", + "but-less-than":"ale mniejsze niż", + "edit-transport-tenant-msg-title":"Edytuj limity szybkości przesyłania komunikatów najemcy", + "edit-transport-tenant-telemetry-msg-title":"Edytuj limity szybkości przesyłania telemetrii komunikatów najemcy", + "edit-transport-tenant-telemetry-data-points-title":"Edytuj limity szybkości przesyłania punktów danych telemetrii najemcy", + "edit-transport-device-msg-title":"Edytuj limity szybkości przesyłania komunikatów urządzenia", + "edit-transport-device-telemetry-msg-title":"Edytuj limity szybkości przesyłania telemetrii komunikatów urządzenia", + "edit-transport-device-telemetry-data-points-title":"Edytuj limity szybkości przesyłania punktów danych telemetrii urządzenia", + "edit-tenant-rest-limits-title":"Edytuj limity szybkości żądań REST dla najemcy", + "edit-customer-rest-limits-title":"Edytuj limity szybkości żądań REST dla klienta", + "edit-ws-limit-updates-per-session-title":"Edytuj limity szybkości aktualizacji WS na sesję", + "edit-cassandra-tenant-limits-configuration-title":"Edytuj limity szybkości zapytań do Cassandry dla najemcy", + "edit-tenant-entity-export-rate-limit-title":"Edytuj limity szybkości tworzenia wersji jednostki dla najemcy", + "edit-tenant-entity-import-rate-limit-title":"Edytuj limity szybkości wczytywania wersji jednostki dla najemcy", + "edit-tenant-notification-request-rate-limit-title":"Edytuj limity szybkości żądań powiadomień dla najemcy", + "edit-tenant-notification-requests-per-rule-rate-limit-title":"Edytuj limity szybkości żądań powiadomień na regułę powiadomień dla najemcy", + "messages-per":"komunikatów na", + "not-set":"Nie ustawiono", + "number-of-messages":"Liczba komunikatów", + "number-of-messages-required":"Wymagana liczba komunikatów.", + "number-of-messages-min":"Minimalna wartość to 1.", + "preview":"Podgląd", + "per-seconds":"Na sekundę", + "per-seconds-required":"Wymagana jest szybkość czasowa.", + "per-seconds-min":"Minimalna wartość to 1.", + "rate-limits":"Limity szybkości", + "remove-limit":"Usuń limit", + "transport-tenant-msg":"Przesyłanie komunikatów najemcy", + "transport-tenant-telemetry-msg":"Przesyłanie telemetrii komunikatów najemcy", + "transport-tenant-telemetry-data-points":"Przesyłanie punktów danych telemetrii najemcy", + "transport-device-msg":"Przesyłanie komunikatów urządzenia", + "transport-device-telemetry-msg":"Przesyłanie telemetrii komunikatów urządzenia", + "transport-device-telemetry-data-points":"Przesyłanie punktów danych telemetrii urządzenia", + "sec":"sek" + } + }, + "timeinterval":{ + "seconds-interval":"{ seconds, plural, =1 {1 sekunda} other {# sekundy} }", + "minutes-interval":"{ minutes, plural, =1 {1 minuta} other {# minuty} }", + "hours-interval":"{ hours, plural, =1 {1 godzina} other {# godziny} }", + "days-interval":"{ days, plural, =1 {1 dzień} other {# dni} }", + "days":"Dni", + "hours":"Godziny", + "minutes":"Minuty", + "seconds":"Sekundy", + "advanced":"Zaawansowane", + "predefined":{ + "yesterday":"Wczoraj", + "day-before-yesterday":"Przedwczoraj", + "this-day-last-week":"Ten dzień w zeszłym tygodniu", + "previous-week":"Poprzedni tydzień (Ndz - Sob)", + "previous-week-iso":"Poprzedni tydzień (Pon - Ndz)", + "previous-month":"Poprzedni miesiąc", + "previous-quarter":"Poprzedni kwartał", + "previous-half-year":"Poprzednie półrocze", + "previous-year":"Poprzedni rok", + "current-hour":"Bieżąca godzina", + "current-day":"Bieżący dzień", + "current-day-so-far":"Bieżący dzień do tej pory", + "current-week":"Bieżący tydzień (Ndz - Sob)", + "current-week-iso":"Bieżący tydzień (Pon - Ndz)", + "current-week-so-far":"Bieżący tydzień do tej pory (Ndz - Sob)", + "current-week-iso-so-far":"Bieżący tydzień do tej pory (Pon - Ndz)", + "current-month":"Bieżący miesiąc", + "current-month-so-far":"Bieżący miesiąc do tej pory", + "current-quarter":"Bieżący kwartał", + "current-quarter-so-far":"Bieżący kwartał do tej pory", + "current-half-year":"Bieżące półrocze", + "current-half-year-so-far":"Bieżące półrocze do tej pory", + "current-year":"Bieżący rok", + "current-year-so-far":"Bieżący rok do tej pory" + }, + "type":{ + "week":"Tydzień (Ndz - Sob)", + "week-iso":"Tydzień (Pon - Ndz)", + "month":"Miesiąc", + "quarter":"Kwartał" + } + }, + "timeunit":{ + "milliseconds":"Milisekundy", + "seconds":"Sekundy", + "minutes":"Minuty", + "hours":"Godziny", + "days":"Dni" + }, + "timewindow":{ + "timewindow":"Okno czasowe", + "years":"{ years, plural, =1 { rok } other {# lata } }", + "years-short":"{{ years }}r", + "months":"{ months, plural, =1 { miesiąc } other {# miesiące } }", + "months-short":"{{ months }}m", + "weeks":"{ weeks, plural, =1 { tydzień } other {# tygodnie } }", + "weeks-short":"{{ weeks }}t", + "days":"{ days, plural, =1 { dzień } other {# dni } }", + "days-short":"{{ days }}d", + "hours":"{ hours, plural, =0 { godzina } =1 {1 godzina } other {# godziny } }", + "hr":"{{ hr }} godz", + "hr-short":"{{ hr }}h", + "minutes":"{ minutes, plural, =0 { minuta } =1 {1 minuta } other {# minuty } }", + "min":"{{ min }} min", + "min-short":"{{ min }}m", + "seconds":"{ seconds, plural, =0 { sekunda } =1 {1 sekunda } other {# sekundy } }", + "sec":"{{ sec }} sek", + "sec-short":"{{ sec }}s", + "short":{ + "days":"{ days, plural, =1 {1 dzień } other {# dni } }", + "hours":"{ hours, plural, =1 {1 godzina } other {# godziny } }", + "minutes":"{{minutes}} min ", + "seconds":"{{seconds}} sek " + }, + "realtime":"Na żywo", + "history":"Historia", + "last-prefix":"ostatni", + "period":"od {{ startTime }} do {{ endTime }}", + "edit":"Edytuj okno czasowe", + "date-range":"Zakres dat", + "for-all-time":"Dla wszystkich dostępnych danych", + "last":"Ostatnie", + "time-period":"Okres czasu", + "hide":"Ukryj", + "interval":"Interwał", + "just-now":"Teraz", + "just-now-lower":"teraz", + "ago":"temu", + "style":"Styl okna czasowego", + "icon":"Ikona", + "icon-position":"Pozycja ikony", + "icon-position-left":"Lewo", + "icon-position-right":"Prawo", + "font":"Czcionka", + "color":"Kolor", + "displayTypePrefix":"Wyświetlaj przedrostek Na żywo/Historia", + "preview":"Podgląd" + }, + "tooltip":{ + "value":"Wartość", + "date":"Data", + "background-color":"Kolor tła", + "background-blur":"Rozmycie tła" + }, + "unit":{ + "millimeter":"Milimetr", + "centimeter":"Centymetr", + "angstrom":"Angstrom", + "nanometer":"Nanometr", + "micrometer":"Mikrometr", + "meter":"Metr", + "kilometer":"Kilometr", + "inch":"Cal", + "foot":"Stopa", + "yard":"Jard", + "mile":"Mila", + "nautical-mile":"Mila morska", + "astronomical-unit":"Jednostka astronomiczna", + "reciprocal-metre":"Odwrócony metr", + "meter-per-meter":"Metr na metr", + "steradian":"Steradian", + "thou":"Thou", + "barleycorn":"Jęczmień", + "hand":"Dłoń", + "chain":"Ciąg", + "furlong":"Furlong", + "league":"Liga", + "fathom":"Fathom", + "cable":"Kabel", + "link":"Link", + "rod":"Ród", + "nanogram":"Nanogram", + "microgram":"Mikrogram", + "milligram":"Miligram", + "gram":"Gram", + "kilogram":"Kilogram", + "tonne":"Tonna", + "ounce":"Uncja", + "pound":"Funt", + "stone":"Stoun", + "hundredweight-count":"Cental", + "short-tons":"Krótka tona", + "dalton":"Dalton", + "grain":"Gran", + "drachm":"Drachma", + "quarter":"Quarter", + "slug":"Slug", + "carat":"Karad", + "cubic-millimeter":"Milimetr sześcienny", + "cubic-centimeter":"Centymetr sześcienny", + "cubic-meter":"Metr sześcienny", + "cubic-kilometer":"Kilometr sześcienny", + "microliter":"Mikrolitr", + "milliliter":"Mililitr", + "liter":"Litr", + "hectoliter":"Hektolitr", + "cubic-inch":"Cal sześcienny", + "cubic-foot":"Stopa sześcienna", + "cubic-yard":"Jard sześcienny", + "fluid-ounce":"Uncja płynu", + "pint":"Pinta", + "quart":"Kwarta", + "gallon":"Galona", + "oil-barrels":"Baryłki naftowe", + "cubic-meter-per-kilogram":"Metr sześcienny na kilogram", + "gill":"Gill", + "hogshead":"Hogshead", + "teaspoon":"Łyżeczka", + "tablespoon":"Łyżka stołowa", + "cup":"Kubek", + "celsius":"Stopnie Celsiusza", + "kelvin":"Kelwin", + "rankine":"Rankine", + "fahrenheit":"Stopnie Fahrenheita", + "percent":"Procent", + "meter-per-second":"Metr na sekundę", + "kilometer-per-hour":"Kilometr na godzinę", + "foot-per-second":"Stopa na sekundę", + "mile-per-hour":"Mila na godzinę", + "knot":"Węzeł", + "millimeters-per-minute":"Milimetrów na minutę", + "kilometer-per-hour-squared":"Kilometr na godzinę do kwadratu", + "foot-per-second-squared":"Stopa na sekundę do kwadratu", + "pascal":"Pascal", + "kilopascal":"Kilopascal", + "megapascal":"Megapascal", + "gigapascal":"Gigapascal", + "millibar":"Milibar", + "bar":"Bar", + "kilobar":"Kilobar", + "newton":"Newton", + "newton-meter":"Newton-metr", + "foot-pounds":"Stopy funtów", + "inch-pounds":"Calowe funty", + "newton-per-meter":"Newton na metr", + "atmospheres":"Atmosfery", + "pounds-per-square-inch":"Funtów na cal kwadratowy", + "torr":"Torr", + "inches-of-mercury":"Cale rtęci", + "pascal-per-square-meter":"Pascal na metr kwadratowy", + "pound-per-square-inch":"Funt na cal kwadratowy", + "newton-per-square-meter":"Newton na metr kwadratowy", + "kilogram-force-per-square-meter":"Kilogram-siła na metr kwadratowy", + "pascal-per-square-centimeter":"Pascal na centymetr kwadratowy", + "ton-force-per-square-inch":"Ton-siła na cal kwadratowy", + "kilonewton-per-square-meter":"Kilonewton na metr kwadratowy", + "newton-per-square-millimeter":"Newton na milimetr kwadratowy", + "microjoule":"Mikrodżul", + "millijoule":"Milidżul", + "joule":"Dżul", + "kilojoule":"Kilodżul", + "megajoule":"Megadżul", + "gigajoule":"Gigadżul", + "watt-hour":"Watogodzina", + "kilowatt-hour":"Kilowatogodzina", + "electron-volts":"Elektronowolt", + "joules-per-coulomb":"Dżule na kulomb", + "british-thermal-unit":"Brytyjska jednostka ciepła", + "foot-pound":"Stopa-funt", + "calorie":"Kaloria", + "small-calorie":"Mała kaloria", + "kilocalorie":"Kilokaloria", + "joule-per-kelvin":"Dżul na kelwin", + "joule-per-kilogram-kelvin":"Dżul na kilogram-kelwin", + "joule-per-kilogram":"Dżul na kilogram", + "watt-per-meter-kelvin":"Wat na metr-kelwin", + "joule-per-cubic-meter":"Dżul na metr sześcienny", + "therm":"Term", + "electric-dipole-moment":"Moment dipolowy elektryczny", + "magnetic-dipole-moment":"Moment dipolowy magnetyczny", + "debye":"Debye", + "coulomb-per-square-meter-per-volt":"Kulomb na metr kwadratowy na wolt", + "milliwatt":"Miliwat", + "microwatt":"Mikrowat", + "watt":"Wat", + "kilowatt":"Kilowat", + "megawatt":"Megawat", + "gigawatt":"Gigawat", + "metric-horsepower":"Koń mechaniczny metryczny", + "milliwatt-per-square-centimeter":"Miliwaty na centymetr kwadratowy", + "watt-per-square-centimeter":"Waty na centymetr kwadratowy", + "kilowatt-per-square-centimeter":"Kilowat na centymetr kwadratowy", + "milliwatt-per-square-meter":"Miliwaty na metr kwadratowy", + "watt-per-square-meter":"Waty na metr kwadratowy", + "kilowatt-per-square-meter":"Kilowaty na metr kwadratowy", + "watt-per-square-inch":"Waty na cal kwadratowy", + "kilowatt-per-square-inch":"Kilowaty na cal kwadratowy", + "horsepower":"Koń mechaniczny", + "btu-per-hour":"Brytyjskie jednostki ciepła na godzinę", + "coulomb":"Kulomb", + "millicoulomb":"Milikulomb", + "microcoulomb":"Mikrokulomb", + "picocoulomb":"Pikokulomb", + "coulomb-per-meter":"Kulomb na metr", + "coulomb-per-cubic-meter":"Kulomb na metr sześcienny", + "coulomb-per-square-meter":"Kulomb na metr kwadratowy", + "square-millimeter":"Metr kwadratowy", + "square-centimeter":"Centymetr kwadratowy", + "square-meter":"Metr kwadratowy", + "hectare":"Hektar", + "square-kilometer":"Kilometr kwadratowy", + "square-inch":"Cal kwadratowy", + "square-foot":"Stopa kwadratowa", + "square-yard":"Jard kwadratowy", + "acre":"Akr", + "square-mile":"Mila kwadratowa", + "are":"Ar", + "barn":"Barn", + "circular-inch":"Cal okrągły", + "milliampere-hour":"Miliamperogodzina", + "milliampere-hour-tags":"prąd elektryczny, przepływ prądu, ładunek elektryczny, pojemność prądu, przepływ elektryczności, przepływ elektryczny, godzina miliamperowa, miliamperogodziny, mAh", + "ampere-hours":"Amperogodziny", + "ampere-hours-tags":"prąd elektryczny, przepływ prądu, ładunek elektryczny, pojemność prądu, przepływ elektryczności, przepływ elektryczny, amper, amperogodziny, Ah", + "kiloampere-hours":"Kiloamperogodziny", + "kiloampere-hours-tags":"prąd elektryczny, przepływ prądu, ładunek elektryczny, pojemność prądu, przepływ elektryczności, przepływ elektryczny, kiloamperogodziny, kiloamperogodzina, kAh", + "nanoampere":"Nanoamper", + "nanoampere-tags":"prąd, ampery, nanoamper, nA", + "picoampere":"Pikoamper", + "picoampere-tags":"prąd, ampery, pikoamper, pA", + "microampere":"Mikroamper", + "microampere-tags":"prąd elektryczny, mikroamper, mikroampery, μA", + "milliampere":"Miliamper", + "milliampere-tags":"prąd elektryczny, miliamper, miliampery, mA", + "ampere":"Amper", + "ampere-tags":"prąd elektryczny, przepływ prądu, przepływ elektryczności, przepływ elektryczny, amper, ampery, amperaż, A", + "kiloamperes":"Kiloampery", + "kiloamperes-tags":"prąd elektryczny, przepływ prądu, kiloampery, kA", + "microampere-per-square-centimeter":"Mikroamper na centymetr kwadratowy", + "microampere-per-square-centimeter-tags":"Gęstość prądu, mikroamper na centymetr kwadratowy, µA/cm²", + "ampere-per-square-meter":"Amper na metr kwadratowy", + "ampere-per-square-meter-tags":"gęstość prądu, prąd na jednostkę powierzchni, amper na metr kwadratowy, A/m²", + "ampere-per-meter":"Amper na metr", + "ampere-per-meter-tags":"siła pola magnetycznego, intensywność pola magnetycznego, amper na metr, A/m", + "oersted":"Oersted", + "oersted-tags":"pole magnetyczne, oersted, Oe", + "bohr-magneton":"Magnetony Bohra", + "bohr-magneton-tags":"fizyka atomowa, moment magnetyczny, magneton Bohra, μB", + "ampere-meter-squared":"Amper-Metr Kwadratowy", + "ampere-meter-squared-tags":"moment magnetyczny, moment dipolowy, amper-metr kwadratowy, A·m²", + "ampere-meter":"Amper-Metr", + "ampere-meter-tags":"pole magnetyczne, pętla prądowa, amper-metr, A·m", + "nanovolt":"Nanowolt", + "picovolt":"Pikowolt", + "millivolts":"Milivolty", + "microvolts":"Mikrowolty", + "volt":"Wolt", + "kilovolts":"Kilowolty", + "dbmV":"dBmV", + "dbm":"dBm", + "volt-meter":"Woltomierz", + "kilovolt-meter":"Kilowoltomierz", + "megavolt-meter":"Megawoltomierz", + "microvolt-meter":"Mikrowoltomierz", + "millivolt-meter":"Milivoltomierz", + "nanovolt-meter":"Nanowoltomierz", + "ohm":"Om", + "microohm":"Mikroom", + "milliohm":"Miloom", + "kilohm":"Kiloom", + "megohm":"Megaom", + "gigohm":"Gigaom", + "hertz":"Herz", + "kilohertz":"Kilohertz", + "megahertz":"Megahertz", + "gigahertz":"Gigahertz", + "rpm":"Obroty na minutę", + "candela-per-square-meter":"Kandela na metr kwadratowy", + "candela":"Kandela", + "lumen":"Lumen", + "lux":"Luks", + "foot-candle":"Stopa-świeca", + "lumen-per-square-meter":"Lumen na metr kwadratowy", + "lux-second":"Luks-sekunda", + "lumen-second":"Lumen-sekunda", + "lumens-per-watt":"Lumeny na wat", + "absorbance":"Absorpcja", + "mole":"Mol", + "nanomole":"Nanomol", + "micromole":"Mikromol", + "millimole":"Milimol", + "kilomole":"Kilomol", + "mole-per-cubic-meter":"Mol na metr sześcienny", + "rssi":"RSSI", + "ppm":"Części na milion", + "ppb":"Części na miliard", + "micrograms-per-cubic-meter":"Mikrogramy na metr sześcienny", + "aqi":"AQI", + "gram-per-cubic-meter":"Gram na metr sześcienny", + "gram-per-kilogram":"Wilgotność właściwa", + "millimeters-per-second":"Milimetry na sekundę", + "neper":"Neper", + "bel":"Bel", + "decibel":"Decybel", + "meters-per-second-squared":"Metry na sekundę kwadratową", + "becquerel":"Bekerel", + "curie":"Kiri", + "gray":"Graj", + "sievert":"Sivert", + "roentgen":"Rentgen", + "cps":"Zliczenia na sekundę", + "rad":"Rad", + "rem":"Rem", + "dps":"Rozpady na sekundę", + "rutherford":"Rutherford", + "coulombs-per-kilogram":"Kulomby na kilogram", + "becquerels-per-cubic-meter":"Bekerel na metr sześcienny", + "curies-per-liter":"Kiri na litr", + "becquerels-per-second":"Bekerel na sekundę", + "curies-per-second":"Kiri na sekundę", + "gy-per-second":"Graj na sekundę", + "watt-per-steradian":"Wat na steradian", + "watt-per-square-metre-steradian":"Wat na metr kwadratowy-steradian", + "ph-level":"Poziom pH", + "turbidity":"Mętność", + "mg-per-liter":"Miligramy na litr", + "microsiemens-per-centimeter":"Mikrosiemeny na centymetr", + "millisiemens-per-meter":"Milisiemeny na metr", + "siemens-per-meter":"Siemeny na metr", + "kilogram-per-cubic-meter":"Kilogram na metr sześcienny", + "gram-per-cubic-centimeter":"Gram na centymetr sześcienny", + "kilogram-per-square-meter":"Kilogram na metr kwadratowy", + "milligram-per-milliliter":"Miligram na mililitr", + "milligram-per-cubic-meter":"Miligram na metr sześcienny", + "pound-per-cubic-foot":"Funt na stopę sześcienną", + "ounces-per-cubic-inch":"Uncje na cal sześcienny", + "tons-per-cubic-yard":"Tony na jard sześcienny", + "particle-density":"Gęstość cząstek", + "kilometers-per-liter":"Kilometry na litr", + "miles-per-gallon":"Mile na galon", + "liters-per-100-km":"Litrów na 100 kilometrów", + "gallons-per-mile":"Galony na milę", + "liters-per-hour":"Litrów na godzinę", + "gallons-per-hour":"Galony na godzinę", + "beats-per-minute":"Uderzenia na minutę", + "millimeters-of-mercury":"Milimetry słupa rtęci", + "milligrams-per-deciliter":"Miligramy na decylitr", + "g-force":"Siła G", + "kilonewton":"Kiloniuton", + "kilogram-force":"Kilogram-siła", + "pound-force":"Funt-siła", + "kilopound-force":"Kilofunt-siła", + "dyne":"Dyn", + "poundal":"Poundal", + "kip":"Kip", + "gal":"Gal", + "gravity":"Przyspieszenie grawitacyjne", + "hectopascal":"Hektopaskal", + "atmosphere":"Atmosfera", + "millibars":"Milibary", + "inch-of-mercury":"Cal słupa rtęci", + "richter-scale":"Skala Richtera", + "second":"Sekunda", + "minute":"Minuta", + "hour":"Godzina", + "day":"Dzień", + "week":"Tydzień", + "month":"Miesiąc", + "year":"Rok", + "cubic-foot-per-minute":"Stopy sześcienne na minutę", + "cubic-meters-per-hour":"Metry sześcienne na godzinę", + "cubic-meters-per-second":"Metry sześcienne na sekundę", + "liter-per-second":"Litr na sekundę", + "liter-per-minute":"Litr na minutę", + "gallons-per-minute":"Galony na minutę", + "cubic-foot-per-second":"Stopa sześcienna na sekundę", + "milliliters-per-minute":"Mililitrów na minutę", + "bit":"Bit", + "byte":"Bajt", + "kilobyte":"Kilobajt", + "megabyte":"Megabajt", + "gigabyte":"Gigabajt", + "terabyte":"Terabajt", + "petabyte":"Petabajt", + "exabyte":"Eksabajt", + "zettabyte":"Zettabajt", + "yottabyte":"Jottabajt", + "bit-per-second":"Bit na sekundę", + "kilobit-per-second":"Kilobit na sekundę", + "megabit-per-second":"Megabit na sekundę", + "gigabit-per-second":"Gigabit na sekundę", + "terabit-per-second":"Terabit na sekundę", + "byte-per-second":"Bajt na sekundę", + "kilobyte-per-second":"Kilobajt na sekundę", + "megabyte-per-second":"Megabajt na sekundę", + "gigabyte-per-second":"Gigabajt na sekundę", + "degree":"Stopień", + "radian":"Radian", + "gradian":"Grad", + "mil":"Mil", + "revolution":"Obrotów", + "siemens":"Siemens", + "millisiemens":"Milisiemens", + "microsiemens":"Mikrosiemens", + "kilosiemens":"Kilosiemens", + "megasiemens":"Megasiemens", + "gigasiemens":"Gigasiemens", + "farad":"Farad", + "millifarad":"Milifarad", + "microfarad":"Mikrofarad", + "nanofarad":"Nanofarad", + "picofarad":"Pikofarad", + "kilofarad":"Kilofarad", + "megafarad":"Megafarad", + "gigafarad":"Gigafarad", + "terfarad":"Terfarad", + "farad-per-meter":"Farad na metr", + "tesla":"Tesla", + "gauss":"Gaus", + "kilogauss":"Kilogaus", + "millitesla":"Millitesla", + "microtesla":"Mikrotesla", + "nanotesla":"Nanotesla", + "kilotesla":"Kilotesla", + "megatesla":"Megatesla", + "millitesla-square-meters":"Millitesla na metry kwadratowe", + "gamma":"Gamma", + "lambda":"Lambda", + "square-meter-per-second":"Metr kwadratowy na sekundę", + "square-centimeter-per-second":"Centymetr kwadratowy na sekundę", + "stoke":"Stoke", + "centistokes":"Centistoke", + "square-foot-per-second":"Stopa kwadratowa na sekundę", + "square-inch-per-second":"Cal kwadratowy na sekundę", + "pascal-second":"Paskal-sekunda", + "centipoise":"Centipoise", + "poise":"Poise", + "reynolds":"Reynolds", + "pound-per-foot-hour":"Funt na stopę kwadratową na godzinę", + "newton-second-per-square-meter":"Newton-sekunda na metr kwadratowy", + "dyne-second-per-square-centimeter":"Dyn-sekunda na centymetr kwadratowy", + "kilogram-per-meter-second":"Kilogram na metr-sekundę", + "tesla-square-meters":"Tesla na metry kwadratowe", + "maxwell":"Maxwell", + "tesla-per-meter":"Tesla na metr", + "gauss-per-centimeter":"Gaus na centymetr", + "weber":"Weber", + "microweber":"Mikroweber", + "milliweber":"Milliweber", + "gauss-square-centimeter":"Gaus na centymetr kwadratowy", + "kilogauss-square-centimeter":"Kilogaus na centymetr kwadratowy", + "henry":"Henry", + "millihenry":"Millihenry", + "microhenry":"Mikrohenry", + "nanohenry":"Nanohenry", + "henry-per-meter":"Henry na metr", + "tesla-meter-per-ampere":"Tesla na metr na amper", + "gauss-per-oersted":"Gaus na Oersted", + "kilogram-per-mole":"Kilogram na mol", + "gram-per-mole":"Gram na mol", + "milligram-per-mole":"Miligram na mol", + "joule-per-mole":"Dżul na mol", + "joule-per-mole-kelvin":"Dżul na mol-kelwin", + "millivolts-per-meter":"Milivolty na metr", + "volts-per-meter":"Volty na metr", + "kilovolts-per-meter":"Kilovolty na metr", + "radian-per-second":"Radian na sekundę", + "radian-per-second-squared":"Radian na sekundę kwadratową", + "revolutions-per-minute-per-second":"Przyspieszenie kątowe", + "revolutions-per-minute-per-second-squared":"Przyspieszenie kątowe", + "deg-per-second":"Stopnie na sekundę", + "degrees-brix":"Stopnie Brix", + "katal":"Katal", + "katal-per-cubic-metre":"Katal na metr sześcienny" + }, + "user":{ + "user":"Użytkownik", + "users":"Użytkownicy", + "customer-users":"Użytkownicy klienta", + "tenant-admins":"Administratorzy najemcy", + "sys-admin":"Administrator systemu", + "tenant-admin":"Administrator najemcy", + "customer":"Klient", + "anonymous":"Anonimowy", + "add":"Dodaj użytkownika", + "delete":"Usuń użytkownika", + "add-user-text":"Dodaj nowego użytkownika", + "no-users-text":"Nie znaleziono użytkowników", + "user-details":"Szczegóły użytkownika", + "delete-user-title":"Czy na pewno chcesz usunąć użytkownika '{{userEmail}}'?", + "delete-user-text":"Uważaj, po potwierdzeniu użytkownik i wszystkie powiązane dane staną się nieodwracalne.", + "delete-users-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 użytkownika} other {# użytkowników} }?", + "delete-users-action-title":"Usuń { count, plural, =1 {1 użytkownika} other {# użytkowników} }", + "delete-users-text":"Uważaj, po potwierdzeniu wszyscy wybrani użytkownicy zostaną usunięci, a wszystkie powiązane dane staną się nieodwracalne.", + "activation-email-sent-message":"E-mail aktywacyjny został wysłany pomyślnie!", + "resend-activation":"Wyślij ponownie aktywację", + "email":"Email", + "email-required":"Email jest wymagany.", + "invalid-email-format":"Nieprawidłowy format email.", + "first-name":"Imię", + "last-name":"Nazwisko", + "description":"Opis", + "default-dashboard":"Domyślny pulpit", + "always-fullscreen":"Zawsze na pełnym ekranie", + "select-user":"Wybierz użytkownika", + "no-users-matching":"Nie znaleziono użytkowników pasujących do '{{entity}}'.", + "user-required":"Użytkownik jest wymagany", + "activation-method":"Metoda aktywacji", + "display-activation-link":"Wyświetl link aktywacyjny", + "send-activation-mail":"Wyślij mail aktywacyjny", + "activation-link":"Link aktywacyjny użytkownika", + "activation-link-text":"Aby aktywować użytkownika, użyj następującego linku aktywacyjnego:", + "copy-activation-link":"Kopiuj link aktywacyjny", + "activation-link-copied-message":"Link aktywacyjny użytkownika został skopiowany do schowka", + "details":"Szczegóły", + "login-as-tenant-admin":"Zaloguj jako Administrator Najemcy", + "login-as-customer-user":"Zaloguj jako Użytkownik Klienta", + "search":"Szukaj użytkowników", + "selected-users":"{ count, plural, =1 {1 użytkownik} other {# użytkowników} } wybrano", + "disable-account":"Wyłącz konto użytkownika", + "enable-account":"Włącz konto użytkownika", + "enable-account-message":"Konto użytkownika zostało pomyślnie włączone!", + "disable-account-message":"Konto użytkownika zostało pomyślnie wyłączone!", + "copyId":"Kopiuj Id użytkownika", + "idCopiedMessage":"Id użytkownika zostało skopiowane do schowka", + "user-list":"Lista użytkowników", + "user-list-required":"Lista użytkowników jest wymagana" + }, + "value":{ + "type":"Typ wartości", + "string":"Ciąg znaków", + "string-value":"Wartość ciągu znaków", + "string-value-required":"Wartość ciągu znaków jest wymagana", + "integer":"Liczba całkowita", + "integer-value":"Wartość liczby całkowitej", + "integer-value-required":"Wartość liczby całkowitej jest wymagana", + "invalid-integer-value":"Nieprawidłowa wartość liczby całkowitej", + "double":"Podwójna", + "double-value":"Wartość podwójna", + "double-value-required":"Wartość podwójna jest wymagana", + "boolean":"Boolean", + "boolean-value":"Wartość Boolean", + "false":"Fałsz", + "true":"Prawda", + "long":"Długa", + "json":"JSON", + "json-value":"Wartość JSON", + "json-value-invalid":"Wartość JSON ma nieprawidłowy format", + "json-value-required":"Wartość JSON jest wymagana." + }, + "version-control":{ + "version-control":"Kontrola wersji", + "management":"Zarządzanie kontrolą wersji", + "search":"Szukaj wersji", + "branch":"Gałąź", + "default":"Domyślna", + "select-branch":"Wybierz gałąź", + "branch-required":"Gałąź jest wymagana", + "create-entity-version":"Utwórz wersję encji", + "version-name":"Nazwa wersji", + "version-name-required":"Nazwa wersji jest wymagana", + "author":"Autor", + "export-relations":"Eksportuj relacje", + "export-attributes":"Eksportuj atrybuty", + "export-credentials":"Eksportuj poświadczenia", + "entity-versions":"Wersje encji", + "versions":"Wersje", + "created-time":"Czas utworzenia", + "version-id":"ID wersji", + "no-entity-versions-text":"Nie znaleziono wersji encji", + "no-versions-text":"Nie znaleziono wersji", + "copy-full-version-id":"Kopiuj pełne ID wersji", + "create-version":"Utwórz wersję", + "creating-version":"Tworzenie wersji... Proszę czekać", + "nothing-to-commit":"Brak zmian do zatwierdzenia", + "restore-version":"Przywróć wersję", + "restore-entity-from-version":"Przywróć encję z wersji '{{versionName}}'", + "restoring-entity-version":"Przywracanie wersji encji... Proszę czekać", + "load-relations":"Załaduj relacje", + "load-attributes":"Załaduj atrybuty", + "load-credentials":"Załaduj poświadczenia", + "compare-with-current":"Porównaj z obecną", + "diff-entity-with-version":"Różnice z wersją encji '{{versionName}}'", + "previous-difference":"Poprzednia różnica", + "next-difference":"Następna różnica", + "current":"Obecna", + "differences":"{ count, plural, =1 {1 różnica} other {# różnice} }", + "create-entities-version":"Utwórz wersję encji", + "default-sync-strategy":"Domyślna strategia synchronizacji", + "sync-strategy-merge":"Scal", + "sync-strategy-overwrite":"Nadpisz", + "entities-to-export":"Encje do eksportu", + "entities-to-restore":"Encje do przywrócenia", + "sync-strategy":"Strategia synchronizacji", + "all-entities":"Wszystkie encje", + "no-entities-to-export-prompt":"Proszę określić encje do eksportu", + "no-entities-to-restore-prompt":"Proszę określić encje do przywrócenia", + "add-entity-type":"Dodaj typ encji", + "remove-all":"Usuń wszystko", + "version-create-result":"{ added, plural, =0 {Nie dodano encji} =1 {Dodano 1 encję} other {Dodano # encji} }.
    { modified, plural, =0 {Nie zmodyfikowano encji} =1 {Zmodyfikowano 1 encję} other {Zmodyfikowano # encji} }.
    { removed, plural, =0 {Nie usunięto encji} =1 {Usunięto 1 encję} other {Usunięto # encji} }.", + "remove-other-entities":"Usuń inne encje", + "find-existing-entity-by-name":"Znajdź istniejącą encję po nazwie", + "restore-entities-from-version":"Przywróć encje z wersji '{{versionName}}'", + "restoring-entities-from-version":"Przywracanie encji... Proszę czekać", + "no-entities-restored":"Nie przywrócono żadnych encji", + "created":"{{created}} utworzonych", + "updated":"{{updated}} zaktualizowanych", + "deleted":"{{deleted}} usuniętych", + "remove-other-entities-confirm-text":"Uważaj! To trwale usunie wszystkie bieżące encje
    nieobecne w wersji, którą chcesz przywrócić.

    Proszę wpisać \"usuń inne encje\", aby potwierdzić.", + "auto-commit-to-branch":"automatyczne zatwierdzenie do gałęzi {{ branch }}", + "default-create-entity-version-name":"Aktualizacja {{entityName}}", + "sync-strategy-merge-hint":"Tworzy lub aktualizuje wybrane encje w repozytorium. Wszystkie inne encje w repozytorium są niezmodyfikowane.", + "sync-strategy-overwrite-hint":"Tworzy lub aktualizuje wybrane encje w repozytorium. Wszystkie inne encje w repozytorium są usunięte.", + "device-credentials-conflict":"Nie udało się załadować urządzenia z zewnętrznym id {{entityId}}
    ponieważ te same poświadczenia są już obecne w bazie danych dla innego urządzenia.
    Rozważ wyłączenie opcji załaduj poświadczenia w formularzu przywracania.", + "missing-referenced-entity":"Nie udało się załadować {{sourceEntityTypeName}} z zewnętrznym id {{sourceEntityId}}
    ponieważ odnosi się do brakującej {{targetEntityTypeName}} z id {{targetEntityId}}.", + "runtime-failed":"Failed: {{message}}", + "auto-commit-settings-read-only-hint":"Funkcja automatycznego zatwierdzania nie działa z włączoną opcją tylko do odczytu w ustawieniach repozytorium." + }, + "widget":{ + "widget-library":"Biblioteka widgetów", + "widget-bundle":"Pakiet widgetów", + "all-bundles":"Wszystkie pakiety", + "select-widgets-bundle":"Wybierz pakiet widgetów", + "widgets":"Widgety", + "all-widgets":"Wszystkie widgety", + "widget":"Widget", + "select-widget":"Wybierz widget", + "no-widgets-matching":"Nie znaleziono widgetów pasujących do '{{entity}}'.", + "no-widgets":"Brak widgetów", + "no-widgets-text":"Nie znaleziono widgetów", + "management":"Zarządzanie widgetami", + "editor":"Edytor widgetów", + "confirm-to-exit-editor-html":"Masz niezapisane ustawienia widgetu.
    Czy na pewno chcesz opuścić tę stronę?", + "widget-type-not-found":"Problem z ładowaniem konfiguracji widgetu.
    Prawdopodobnie powiązany typ widgetu został usunięty.", + "widget-type-load-error":"Widget nie został załadowany z powodu następujących błędów:", + "remove":"Usuń widget", + "delete":"Usuń widget", + "edit":"Edytuj widget", + "remove-widget-title":"Czy na pewno chcesz usunąć widget '{{widgetTitle}}'?", + "remove-widget-text":"Po potwierdzeniu widget i wszystkie powiązane dane staną się nieodwracalne.", + "timeseries":"Serie czasowe", + "search-data":"Szukaj danych", + "no-data-found":"Nie znaleziono danych", + "latest":"Najnowsze wartości", + "rpc":"Widget sterowania", + "alarm":"Widget alarmów", + "static":"Widget statyczny", + "timeseries-short":"serie", + "latest-short":"najnowsze", + "rpc-short":"sterowanie", + "alarm-short":"alarm", + "static-short":"statyczny", + "select-widget-type":"Wybierz typ widgetu", + "missing-widget-title-error":"Tytuł widgetu musi być określony!", + "widget-saved":"Widget zapisany", + "unable-to-save-widget-error":"Nie można zapisać widgetu! Widget zawiera błędy!", + "save":"Zapisz widget", + "saveAs":"Zapisz widget jako", + "move":"Przenieś widget", + "save-widget-as":"Zapisz widget jako", + "save-widget-as-text":"Proszę wprowadzić nowy tytuł widgetu", + "toggle-fullscreen":"Przełącz pełny ekran", + "run":"Uruchom widget", + "widget-title":"Tytuł widgetu", + "title":"Tytuł", + "title-required":"Tytuł widgetu jest wymagany.", + "title-max-length":"Tytuł powinien mieć mniej niż 256 znaków", + "system":"System", + "type":"Typ widgetu", + "resources":"Zasoby", + "resource-url":"URL JavaScript/CSS", + "resource-is-module":"Jest modułem", + "remove-resource":"Usuń zasób", + "add-resource":"Dodaj zasób", + "html":"HTML", + "tidy":"Uporządkuj", + "css":"CSS", + "settings-schema":"Schemat ustawień", + "datakey-settings-schema":"Schemat ustawień klucza danych", + "latest-datakey-settings-schema":"Schemat ustawień najnowszego klucza danych", + "widget-settings":"Ustawienia widgetu", + "description":"Opis", + "tags":"Tagi", + "image-preview":"Podgląd obrazu", + "settings-form-selector":"Selektor formularza ustawień", + "data-key-settings-form-selector":"Selektor formularza ustawień klucza danych", + "latest-data-key-settings-form-selector":"Selektor formularza ustawień najnowszego klucza danych", + "all":"Wszystkie", + "actual":"Aktualne", + "deprecated":"Przestarzałe", + "has-basic-mode":"Ma tryb podstawowy", + "basic-mode-form-selector":"Selektor formularza trybu podstawowego", + "basic-mode":"Podstawowy", + "advanced-mode":"Zaawansowany", + "javascript":"Javascript", + "js":"JS", + "delete-widget-title":"Czy na pewno chcesz usunąć widget '{{widgetName}}'?", + "delete-widget-text":"Po potwierdzeniu widget i wszystkie powiązane dane staną się nieodwracalne.", + "delete-widgets-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 widget} other {# widgetów} }?", + "delete-widgets-text":"Uważaj, po potwierdzeniu wszystkie wybrane widgety zostaną usunięte, a wszystkie powiązane dane staną się nieodwracalne.", + "delete-widget":"Usuń widget", + "widget-template-load-failed-error":"Nie udało się załadować szablonu widgetu!", + "details":"Szczegóły", + "widget-details":"Szczegóły widgetu", + "add":"Dodaj widget", + "add-existing-widget":"Dodaj istniejący widget", + "add-new-widget":"Dodaj nowy widget", + "search-widgets":"Szukaj widgetów", + "selected-widgets":"{ count, plural, =1 {1 widget} other {# widgetów} } wybrano", + "undo":"Cofnij zmiany widgetu", + "export":"Eksportuj widget", + "export-widgets":"Eksportuj widgety", + "import":"Importuj widget", + "no-data":"Brak danych do wyświetlenia na widżecie", + "data-overflow":"Widget wyświetla {{count}} z {{total}} encji", + "alarm-data-overflow":"Widget wyświetla alarmy dla {{allowedEntities}} (maksymalna dozwolona) encji z {{totalEntities}} encji", + "search":"Szukaj widgetu", + "filter":"Typ filtru widgetu", + "loading-widgets":"Ładowanie widgetów...", + "widget-template-error":"Nieprawidłowy szablon HTML widgetu." + }, + "widget-action":{ + "header-button":"Przycisk nagłówka widgetu", + "open-dashboard-state":"Przejdź do nowego stanu pulpitów", + "update-dashboard-state":"Zaktualizuj bieżący stan pulpitów", + "open-dashboard":"Przejdź do innego pulpitu", + "custom":"Akcja niestandardowa", + "custom-pretty":"Akcja niestandardowa (z szablonem HTML)", + "custom-pretty-error-title":"Błąd niestandardowego okna dialogowego", + "custom-pretty-template-error":"Nieprawidłowy szablon niestandardowego okna dialogowego.", + "custom-pretty-controller-error":"Wystąpił błąd podczas oceny funkcji niestandardowego okna dialogowego.", + "mobile-action":"Akcja mobilna", + "target-dashboard-state":"Docelowy stan pulpitu", + "target-dashboard-state-required":"Docelowy stan pulpitu jest wymagany", + "set-entity-from-widget":"Ustaw encję z widgetu", + "target-dashboard":"Docelowy pulpit", + "open-right-layout":"Otwórz prawy układ pulpitu (widok mobilny)", + "state-display-type":"Opcja wyświetlania stanu pulpitu", + "open-normal":"Normalnie", + "open-in-separate-dialog":"Otwórz w osobnym oknie dialogowym", + "open-in-popover":"Otwórz w dymku", + "dialog-title":"Tytuł okna dialogowego", + "dialog-hide-dashboard-toolbar":"Ukryj pasek narzędzi pulpitu w oknie dialogowym", + "dialog-width":"Szerokość okna dialogowego w procentach względem szerokości viewportu", + "dialog-height":"Wysokość okna dialogowego w procentach względem wysokości viewportu", + "dialog-size-range-error":"Wartość procentowa rozmiaru okna dialogowego powinna być w zakresie od 1 do 100.", + "popover-preferred-placement":"Preferowane umiejscowienie dymka", + "popover-placement-top":"Góra", + "popover-placement-topLeft":"Góra lewo", + "popover-placement-topRight":"Góra prawo", + "popover-placement-right":"Prawo", + "popover-placement-rightTop":"Prawo góra", + "popover-placement-rightBottom":"Prawo dół", + "popover-placement-bottom":"Dół", + "popover-placement-bottomLeft":"Dół lewo", + "popover-placement-bottomRight":"Dół prawo", + "popover-placement-left":"Lewo", + "popover-placement-leftTop":"Lewo góra", + "popover-placement-leftBottom":"Lewo dół", + "popover-hide-on-click-outside":"Ukryj dymek po kliknięciu na zewnątrz", + "popover-hide-dashboard-toolbar":"Ukryj pasek narzędzi pulpitu w dymku", + "popover-width":"Szerokość dymka w jednostkach przeglądarki (np. 100px, 25vw)", + "popover-height":"Wysokość dymka w jednostkach przeglądarki (np. 100px, 25vh)", + "popover-style":"Styl dymka", + "open-new-browser-tab":"Otwórz w nowej karcie przeglądarki", + "mobile":{ + "action-type":"Typ akcji mobilnej", + "action-type-required":"Typ akcji mobilnej jest wymagany", + "take-picture-from-gallery":"Wybierz zdjęcie z galerii", + "take-photo":"Zrób zdjęcie", + "map-direction":"Otwórz wskazówki dojazdu na mapie", + "map-location":"Otwórz lokalizację na mapie", + "scan-qr-code":"Skanuj kod QR", + "make-phone-call":"Wykonaj połączenie telefoniczne", + "get-location":"Pobierz lokalizację telefonu", + "take-screenshot":"Zrób zrzut ekranu" + } + }, + "widgets-bundle":{ + "current":"Aktualny zestaw", + "widgets-bundles":"Zestawy widżetów", + "widgets-bundle-widgets":"Widżety zestawu widżetów", + "add":"Dodaj zestaw widżetów", + "delete":"Usuń zestaw widżetów", + "title":"Tytuł", + "title-required":"Tytuł jest wymagany.", + "title-max-length":"Tytuł powinien być krótszy niż 256", + "description":"Opis", + "image-preview":"Podgląd obrazu", + "order":"Kolejność", + "add-widgets-bundle-text":"Dodaj nowy zestaw widżetów", + "no-widgets-bundles-text":"Nie znaleziono zestawów widżetów", + "empty":"Zestaw widżetów jest pusty", + "details":"Szczegóły", + "widgets-bundle-details":"Szczegóły zestawu widżetów", + "delete-widgets-bundle-title":"Czy na pewno chcesz usunąć zestaw widżetów '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text":"Uważaj, po potwierdzeniu zestaw widżetów oraz wszystkie powiązane dane staną się nieodwracalne.", + "delete-widgets-bundles-title":"Czy na pewno chcesz usunąć { count, plural, =1 {1 zestaw widżetów} other {# zestawy widżetów} }?", + "delete-widgets-bundles-action-title":"Usuń { count, plural, =1 {1 zestaw widżetów} other {# zestawy widżetów} }", + "delete-widgets-bundles-text":"Uważaj, po potwierdzeniu wszystkie wybrane zestawy widżetów zostaną usunięte, a wszystkie powiązane dane staną się nieodwracalne.", + "no-widgets-bundles-matching":"Nie znaleziono zestawów widżetów pasujących do '{{widgetsBundle}}'.", + "widgets-bundle-required":"Zestaw widżetów jest wymagany.", + "system":"System", + "import":"Importuj zestaw widżetów", + "export":"Eksportuj zestaw widżetów", + "export-widgets-bundle-widgets-prompt":"Dołącz widżety zestawu w eksportowanych danych (w przeciwnym razie zostaną wyeksportowane tylko odniesienia do pełnych kwalifikacji nazw widżetów)", + "export-failed-error":"Nie można wyeksportować zestawu widżetów: {{error}}", + "create-new-widgets-bundle":"Utwórz nowy zestaw widżetów", + "widgets-bundle-file":"Plik zestawu widżetów", + "invalid-widgets-bundle-file-error":"Nie można zaimportować zestawu widżetów: Nieprawidłowa struktura danych zestawu widżetów.", + "search":"Szukaj zestawów widżetów", + "selected-widgets-bundles":"{ count, plural, =1 {1 wybrany zestaw widżetów} other {# wybrane zestawy widżetów} }", + "open-widgets-bundle":"Otwórz zestaw widżetów", + "loading-widgets-bundles":"Ładowanie zestawów widżetów..." + }, + "widget-config":{ + "data":"Dane", + "settings":"Ustawienia", + "advanced":"Zaawansowane", + "appearance":"Wygląd", + "widget-card":"Karta widżetu", + "mobile":"Mobilny", + "title":"Tytuł", + "title-tooltip":"Podpowiedź tytułu", + "general-settings":"Ustawienia ogólne", + "display-title":"Wyświetl tytuł widżetu", + "card-title":"Tytuł karty", + "drop-shadow":"Cień", + "enable-fullscreen":"Włącz pełny ekran", + "background-color":"Kolor tła", + "text-color":"Kolor tekstu", + "border-radius":"Promień obramowania", + "padding":"Odstęp", + "margin":"Margines", + "widget-style":"Styl widżetu", + "widget-css":"CSS widżetu", + "title-style":"Styl tytułu", + "mobile-mode-settings":"Ustawienia trybu mobilnego", + "order":"Kolejność", + "height":"Wysokość", + "mobile-hide":"Ukryj widżet w trybie mobilnym", + "desktop-hide":"Ukryj widżet w trybie desktopowym", + "units":"Specjalny symbol obok wartości", + "units-by-default":"Domyślne jednostki", + "decimals":"Liczba miejsc po przecinku", + "decimals-by-default":"Domyślna liczba miejsc po przecinku", + "default-data-key-parameter-hint":"Ten parametr dotyczy wszystkich wartości widżetu, chyba że zostanie nadpisany przez konfigurację klucza danych", + "units-short":"Jednostki", + "decimals-short":"Miejsca po przecinku", + "decimals-suffix":"miejsca po przecinku", + "timewindow":"Okno czasowe", + "use-dashboard-timewindow":"Użyj okna czasowego dashboardu", + "use-widget-timewindow":"Użyj okna czasowego widżetu", + "display-timewindow":"Wyświetl okno czasowe", + "legend":"Legenda", + "display-legend":"Wyświetl legendę", + "datasources":"Źródła danych", + "datasource":"Źródło danych", + "maximum-datasources":"Maksymalnie { count, plural, =1 {1 źródło danych jest dozwolone.} other {# źródeł danych jest dozwolonych} }", + "timeseries-key-error":"Należy określić co najmniej jeden klucz danych szeregów czasowych", + "datasource-type":"Typ", + "datasource-parameters":"Parametry", + "remove-datasource":"Usuń źródło danych", + "add-datasource":"Dodaj źródło danych", + "target-device":"Docelowe urządzenie", + "alarm-source":"Źródło alarmu", + "actions":"Akcje", + "action":"Akcja", + "add-action":"Dodaj akcję", + "search-actions":"Szukaj akcji", + "no-actions-text":"Nie znaleziono akcji", + "action-source":"Źródło akcji", + "action-source-required":"Źródło akcji jest wymagane.", + "action-name":"Nazwa", + "action-name-required":"Nazwa akcji jest wymagana.", + "action-name-not-unique":"Inna akcja o tej samej nazwie już istnieje.
    Nazwa akcji powin.", + "action-icon":"Ikona", + "show-hide-action-using-function":"Pokaż/ukryj akcję za pomocą funkcji", + "action-type":"Typ", + "action-type-required":"Typ akcji jest wymagany.", + "edit-action":"Edytuj akcję", + "delete-action":"Usuń akcję", + "delete-action-title":"Usuń akcję widżetu", + "delete-action-text":"Czy na pewno chcesz usunąć akcję widżetu o nazwie '{{actionName}}'?", + "title-icon":"Ikona tytułu", + "display-icon":"Wyświetl ikonę tytułu", + "card-icon":"Ikona karty", + "icon":"Ikona", + "icon-color":"Kolor ikony", + "icon-size":"Rozmiar ikony", + "advanced-settings":"Zaawansowane ustawienia", + "data-settings":"Ustawienia danych", + "limits":"Limity", + "no-data-display-message":"Alternatywny komunikat \"Brak danych do wyświetlenia\"", + "data-page-size":"Maksymalna liczba encji na źródło danych", + "settings-component-not-found":"Nie znaleziono komponentu formularza ustawień dla selektora '{{selector}}'", + "preview":"Podgląd", + "set":"Ustaw", + "set-message":"Ustaw komunikat", + "advanced-title-style":"Zaawansowany styl tytułu", + "card-style":"Styl karty", + "text":"Tekst", + "background":"Tło", + "advanced-widget-style":"Zaawansowany styl widżetu", + "card-buttons":"Przyciski karty", + "show-card-buttons":"Pokaż przyciski karty", + "card-border-radius":"Promień obramowania karty", + "card-appearance":"Wygląd karty", + "color":"Kolor", + "tooltip":"Podpowiedź", + "units-required":"Jednostka jest wymagana." + }, + "widget-type":{ + "import":"Importuj typ widżetu", + "export":"Eksportuj typ widżetu", + "export-failed-error":"Nie można wyeksportować widżetu: {{error}}", + "widget-file":"Plik widżetu", + "invalid-widget-file-error":"Nie można zaimportować widżetu: Nieprawidłowa struktura danych widżetu." + }, + "widgets":{ + "background":{ + "background":"Tło", + "background-settings":"Ustawienia tła", + "background-type-image":"Obraz", + "background-type-color":"Kolor", + "image-url":"URL obrazu", + "overlay":"Nakładka", + "enable-overlay":"Włącz nakładkę", + "blur":"Rozmycie", + "preview":"Podgląd" + }, + "bar-chart":{ + "bar-appearance":"Wygląd słupka", + "label-on-bar":"Etykieta na słupku", + "value-on-bar":"Wartość na słupku", + "bar-chart-card-style":"Styl karty wykresu słupkowego" + }, + "battery-level":{ + "layout":"Układ", + "layout-vertical-solid":"Pionowy. Stały", + "layout-horizontal-solid":"Poziomy. Stały", + "layout-vertical-divided":"Pionowy. Podzielony", + "layout-horizontal-divided":"Poziomy. Podzielony", + "icon":"Ikona", + "value":"Wartość", + "auto-scale":"Automatyczne skalowanie", + "battery-level-color":"Kolor poziomu baterii", + "battery-shape-color":"Kolor kształtu baterii", + "battery-level-card-style":"Styl karty poziomu baterii", + "sections-count":"Liczba sekcji" + }, + "signal-strength":{ + "value":"Wartość", + "last-update":"Ostatnia aktualizacja", + "no-signal":"Brak sygnału", + "layout":"Układ", + "layout-wifi":"Wi-Fi", + "layout-cellular-bar":"Słupkowy komórkowy", + "icon":"Ikona", + "date":"Data", + "active-bars-color":"Kolor aktywnych pasków sygnału", + "inactive-bars-color":"Kolor nieaktywnych pasków sygnału", + "signal-strength-card-style":"Styl karty siły sygnału" + }, + "chart":{ + "common-settings":"Wspólne ustawienia", + "enable-stacking-mode":"Włącz tryb układania", + "selection":"Wybór zakresu czasowego", + "enable-selection-mode":"Włącz tryb wyboru", + "line-shadow-size":"Rozmiar cienia linii", + "display-smooth-lines":"Wyświetlaj gładkie (krzywe) linie", + "default-bar-width":"Domyślna szerokość słupka dla danych niezagregowanych (milisekundy)", + "bar-alignment":"Wyrównanie słupka", + "bar-alignment-left":"Lewo", + "bar-alignment-right":"Prawo", + "bar-alignment-center":"Środek", + "default-font":"Domyślna czcionka", + "default-font-size":"Domyślny rozmiar czcionki", + "default-font-color":"Domyślny kolor czcionki", + "thresholds-line-width":"Domyślna szerokość linii dla wszystkich progów", + "tooltip-settings":"Ustawienia podpowiedzi", + "tooltip":"Podpowiedź", + "show-tooltip":"Pokaż podpowiedź", + "hover-individual-points":"Najedź na pojedyncze punkty", + "show-cumulative-values":"Pokaż wartości kumulatywne w trybie układania", + "hide-zero-false-values":"Ukryj zera/fałszywe wartości z podpowiedzi", + "tooltip-value-format-function":"Funkcja formatująca wartość podpowiedzi", + "grid-settings":"Ustawienia siatki", + "show-vertical-lines":"Pokaż linie pionowe", + "show-horizontal-lines":"Pokaż linie poziome", + "grid-outline-border-width":"Szerokość krawędzi/ramki siatki (px)", + "primary-color":"Kolor podstawowy", + "background-color":"Kolor tła", + "ticks-color":"Kolor znaczników", + "xaxis-settings":"Ustawienia osi X", + "axis-title":"Tytuł osi", + "xaxis-tick-labels-settings":"Ustawienia etykiet osi X", + "show-tick-labels":"Pokaż etykiety osi", + "yaxis-settings":"Ustawienia osi Y", + "min-scale-value":"Minimalna wartość na skali", + "max-scale-value":"Maksymalna wartość na skali", + "yaxis-tick-labels-settings":"Ustawienia etykiet osi Y", + "tick-step-size":"Rozmiar kroku między znacznikami", + "number-of-decimals":"Liczba miejsc po przecinku do wyświetlenia", + "ticks-formatter-function":"Funkcja formatująca znaczniki", + "comparison-settings":"Ustawienia porównywania", + "enable-comparison":"Włącz porównywanie", + "time-for-comparison":"Okres porównywania", + "time-for-comparison-previous-interval":"Poprzedni interwał (domyślnie)", + "time-for-comparison-days":"Dzień temu", + "time-for-comparison-weeks":"Tydzień temu", + "time-for-comparison-months":"Miesiąc temu", + "time-for-comparison-years":"Rok temu", + "time-for-comparison-custom-interval":"Niestandardowy interwał", + "custom-interval-value":"Wartość niestandardowego interwału (ms)", + "comparison-x-axis-settings":"Ustawienia osi X porównywania", + "axis-position":"Pozycja osi", + "axis-position-top":"Góra (domyślnie)", + "axis-position-bottom":"Dół", + "custom-legend-settings":"Niestandardowe ustawienia legendy", + "enable-custom-legend":"Włącz niestandardową legendę (pozwoli to na korzystanie z atrybutów/czasu rzeczywistego w etykietach kluczy)", + "key-name":"Nazwa klucza", + "key-name-required":"Nazwa klucza jest wymagana", + "key-type":"Typ klucza", + "key-type-attribute":"Atrybut", + "key-type-timeseries":"Czas rzeczywisty", + "label-keys-list":"Lista kluczy do użycia w etykietach", + "no-label-keys":"Brak skonfigurowanych kluczy", + "add-label-key":"Dodaj nowy klucz", + "line-width":"Szerokość linii", + "color":"Kolor", + "data-is-hidden-by-default":"Dane są domyślnie ukryte", + "disable-data-hiding":"Wyłącz ukrywanie danych", + "remove-from-legend":"Usuń klucz danych z legendy", + "exclude-from-stacking":"Wyłącz z układania (dostępne w trybie „Układanie”)", + "line-settings":"Ustawienia linii", + "show-line":"Pokaż linię", + "fill-line":"Wypełnij linię", + "fill-line-opacity":"Przezroczystość wypełnienia linii", + "points-settings":"Ustawienia punktów", + "show-points":"Pokaż punkty", + "points-line-width":"Szerokość linii punktów", + "points-radius":"Promień punktów", + "point-shape":"Kształt punktu", + "point-shape-circle":"Okrąg", + "point-shape-cross":"Krzyż", + "point-shape-diamond":"Diament", + "point-shape-square":"Kwadrat", + "point-shape-triangle":"Trójkąt", + "point-shape-custom":"Niestandardowa funkcja", + "point-shape-draw-function":"Funkcja rysowania kształtu punktu", + "show-separate-axis":"Pokaż oddzielną oś", + "axis-position-left":"Lewo", + "axis-position-right":"Prawo", + "thresholds":"Progi", + "no-thresholds":"Brak skonfigurowanych progów", + "add-threshold":"Dodaj próg", + "show-values-for-comparison":"Pokaż historyczne wartości do porównania", + "comparison-values-label":"Etykieta historycznych wartości", + "comparison-line-color":"Kolor linii porównawczej", + "threshold-settings":"Ustawienia progu", + "use-as-threshold":"Użyj wartości klucza jako progu", + "threshold-line-width":"Szerokość linii progu", + "threshold-color":"Kolor progu", + "common-pie-settings":"Wspólne ustawienia wykresu kołowego", + "radius":"Promień", + "inner-radius":"Wewnętrzny promień", + "tilt":"Nachylenie", + "common-pie-settings-range-error":"Wartość powinna być w zakresie od 0 do 1", + "stroke-settings":"Ustawienia obrysu", + "width-pixels":"Szerokość (piksele)", + "show-labels":"Pokaż etykiety", + "animation-settings":"Ustawienia animacji", + "animated-pie":"Włącz animację kołową (eksperymentalne)", + "border-settings":"Ustawienia obramowania", + "border-width":"Szerokość obramowania", + "border-color":"Kolor obramowania", + "legend-settings":"Ustawienia legendy", + "display-legend":"Wyświetl legendę", + "labels-font-color":"Kolor czcionki etykiet", + "series":"Seria", + "add-series":"Dodaj serię", + "series-settings":"Ustawienia serii", + "remove-series":"Usuń serię", + "no-series":"Brak skonfigurowanych serii", + "no-series-error":"Należy określić co najmniej jedną serię", + "chart-appearance":"Wygląd wykresu", + "vertical-grid-lines":"Linie siatki pionowej", + "horizontal-grid-lines":"Linie siatki poziomej", + "chart-background":"Tło wykresu", + "grid-lines-color":"Kolor linii siatki", + "border":"Obramowanie", + "axis":"Oś", + "vertical-axis":"Oś pionowa", + "ticks":"Znaczniki", + "horizontal-axis":"Oś pozioma" + }, + "color":{ + "color-settings":"Ustawienia koloru", + "color-type-constant":"Stały", + "color-type-range":"Zakres", + "color-type-function":"Funkcja", + "color":"Kolor", + "value-range":"Zakres wartości", + "from":"Od", + "to":"Do", + "color-function":"Funkcja koloru", + "copy-color-settings-from":"Skopiuj ustawienia koloru z" + }, + "dashboard-state":{ + "dashboard-state-settings":"Ustawienia stanu pulpitu", + "dashboard-state":"Identyfikator stanu pulpitu", + "autofill-state-layout":"Domyślne wypełnianie wysokości układu stanu automatycznie", + "default-margin":"Domyślny margines widżetów", + "default-background-color":"Domyślny kolor tła", + "sync-parent-state-params":"Synchronizuj parametry stanu z nadrzędnym pulpitem" + }, + "date-range-picker-settings":{ + "date-range-picker-settings":"Ustawienia wybieraka zakresu dat", + "hide-date-range-picker":"Ukryj wybierak zakresu dat", + "picker-one-panel":"Wybierak zakresu dat jednopanelowy", + "picker-auto-confirm":"Automatyczne potwierdzanie wybieraka zakresu dat", + "picker-show-template":"Pokazuj szablon wybieraka zakresu dat", + "first-day-of-week":"Pierwszy dzień tygodnia", + "interval-settings":"Ustawienia interwału", + "hide-interval":"Ukryj interwał", + "initial-interval":"Początkowy interwał", + "interval-hour":"Godzina", + "interval-day":"Dzień", + "interval-week":"Tydzień", + "interval-two-weeks":"2 tygodnie", + "interval-month":"Miesiąc", + "interval-three-months":"3 miesiące", + "interval-six-months":"6 miesięcy", + "step-settings":"Ustawienia kroku", + "hide-step-size":"Ukryj rozmiar kroku", + "initial-step-size":"Początkowy rozmiar kroku", + "hide-labels":"Ukryj etykiety", + "use-session-storage":"Użyj przechowywania sesji", + "localizationMap":{ + "Sun":"Niedz.", + "Mon":"Pon.", + "Tue":"Wt.", + "Wed":"Śr.", + "Thu":"Czw.", + "Fri":"Piąt.", + "Sat":"Sob.", + "Jan":"Sty", + "Feb":"Lut", + "Mar":"Mar", + "Apr":"Kwi", + "May":"Maj", + "Jun":"Cze", + "Jul":"Lip", + "Aug":"Sie", + "Sep":"Wrz", + "Oct":"Paź", + "Nov":"Lis", + "Dec":"Gru", + "January":"Styczeń", + "February":"Luty", + "March":"Marzec", + "April":"Kwiecień", + "June":"Czerwiec", + "July":"Lipiec", + "August":"Sierpień", + "September":"Wrzesień", + "October":"Październik", + "November":"Listopad", + "December":"Grudzień", + "Custom Date Range":"Niestandardowy zakres dat", + "Date Range Template":"Szablon zakresu dat", + "Today":"Dziś", + "Yesterday":"Wczoraj", + "This Week":"Bieżący tydzień", + "Last Week":"Ostatni tydzień", + "This Month":"Bieżący miesiąc", + "Last Month":"Ostatni miesiąc", + "Year":"Rok", + "This Year":"Bieżący rok", + "Last Year":"Ostatni rok", + "Date picker":"Wybierak dat", + "Hour":"Godzina", + "Day":"Dzień", + "Week":"Tydzień", + "2 weeks":"2 Tygodnie", + "Month":"Miesiąc", + "3 months":"3 Miesiące", + "6 months":"6 Miesięcy", + "Custom interval":"Niestandardowy interwał", + "Interval":"Interwał", + "Step size":"Rozmiar kroku", + "Ok":"Ok" + } + }, + "doughnut":{ + "total":"Suma", + "layout":"Układ", + "layout-default":"Domyślny", + "layout-with-total":"Z sumą", + "auto-scale":"Automatyczne skalowanie", + "clockwise-layout":"Układ zgodny z ruchem wskazówek zegara", + "sort-series":"Sortuj serie według etykiety", + "central-total-value":"Centralna wartość sumy", + "tooltip-value-type-absolute":"Bezwzględna", + "tooltip-value-type-percentage":"Procentowa", + "doughnut-card-style":"Styl karty pączka" + }, + "entities-hierarchy":{ + "hierarchy-data-settings":"Ustawienia danych hierarchii", + "relations-query-function":"Funkcja zapytania o relacje węzła", + "has-children-function":"Funkcja sprawdzająca, czy węzeł ma dzieci", + "node-state-settings":"Ustawienia stanu węzła", + "node-opened-function":"Domyślna funkcja otwierania węzła", + "node-disabled-function":"Funkcja dezaktywacji węzła", + "display-settings":"Ustawienia wyświetlania", + "node-icon-function":"Funkcja ikony węzła", + "node-text-function":"Funkcja tekstu węzła", + "sort-settings":"Ustawienia sortowania", + "nodes-sort-function":"Funkcja sortowania węzłów" + }, + "edge":{ + "display-default-title":"Wyświetl domyślny tytuł" + }, + "gateway":{ + "general-settings":"Ustawienia ogólne", + "widget-title":"Tytuł widżetu", + "default-archive-file-name":"Domyślna nazwa pliku archiwum", + "device-type-for-new-gateway":"Typ urządzenia dla nowej bramy", + "messages-settings":"Ustawienia komunikatów", + "save-config-success-message":"Komunikat tekstowy o pomyślnie zapisanej konfiguracji bramy", + "device-name-exists-message":"Komunikat tekstowy, gdy urządzenie o podanej nazwie już istnieje", + "gateway-title":"Formularz bramy", + "read-only":"Tylko do odczytu", + "events-title":"Tytuł formularza zdarzeń bramy", + "events-filter":"Filtr zdarzeń", + "event-key-contains":"Klucz zdarzenia zawiera...", + "show-connector":"Pokaż dla konektora", + "connector-state-param-key":"Klucz parametru stanu konektora", + "status":"Status", + "message":"Wiadomość", + "created-time":"Czas utworzenia" + }, + "gauge":{ + "default-color":"Domyślny kolor", + "radial-gauge-settings":"Ustawienia miernika radialnego", + "ticks-settings":"Ustawienia podziałek", + "min-value":"Wartość minimalna", + "max-value":"Wartość maksymalna", + "min-value-short":"min", + "max-value-short":"max", + "start-ticks-angle":"Kąt początkowy podziałek", + "ticks-angle":"Kąt podziałek", + "major-ticks":"Podziałki główne", + "major-ticks-count":"Liczba podziałek głównych", + "major-ticks-color":"Kolor podziałek głównych", + "minor-ticks":"Podziałki dodatkowe", + "minor-ticks-count":"Liczba podziałek dodatkowych", + "minor-ticks-color":"Kolor podziałek dodatkowych", + "tick-numbers-font":"Czcionka numerów podziałek", + "unit-title-settings":"Ustawienia jednostki", + "show-unit-title":"Pokaż nazwę jednostki", + "unit-title":"Nazwa jednostki", + "title-font":"Czcionka tytułu", + "units-settings":"Ustawienia jednostek", + "units-font":"Czcionka jednostek", + "value-box-settings":"Ustawienia pola wartości", + "show-value-box":"Pokaż pole wartości", + "value-box":"Pole wartości", + "value-int":"Liczba cyfr dla części całkowitej wartości", + "value-text":"Tekst wartości", + "value-text-shadow":"Cień tekstu wartości", + "value-font":"Czcionka tekstu wartości", + "rect-stroke-color-start":"Kolor obwódki prostokąta - początek gradientu", + "rect-stroke-color-end":"Kolor obwódki prostokąta - koniec gradientu", + "background-color":"Kolor tła", + "shadow-color":"Kolor cienia", + "value-box-rect-stroke-color":"Kolor obwódki prostokąta pola wartości", + "value-box-rect-stroke-color-end":"Kolor obwódki prostokąta pola wartości - koniec gradientu", + "value-box-background-color":"Kolor tła pola wartości", + "value-box-shadow-color":"Kolor cienia pola wartości", + "plate-settings":"Ustawienia tarczy", + "show-plate-border":"Pokaż krawędź tarczy", + "plate-color":"Kolor tarczy", + "needle-settings":"Ustawienia wskazówki", + "needle-circle-size":"Rozmiar koła wskazówki", + "needle-color":"Kolor wskazówki", + "needle-color-start":"Kolor wskazówki - początek gradientu", + "needle-color-end":"Kolor wskazówki - koniec gradientu", + "needle-color-shadow-up":"Kolor górnej części cienia wskazówki", + "needle-color-shadow-down":"Cień", + "highlights-settings":"Ustawienia punktów kulminacyjnych", + "highlights-width":"Szerokość punktów kulminacyjnych", + "highlights":"Punkty kulminacyjne", + "highlight-from":"Od", + "highlight-to":"Do", + "highlight-color":"Kolor", + "no-highlights":"Brak skonfigurowanych punktów kulminacyjnych", + "add-highlight":"Dodaj punkt kulminacyjny", + "animation-settings":"Ustawienia animacji", + "enable-animation":"Animacja", + "animation-duration-rule":"Czas trwania i reguła animacji", + "animation-duration":"Czas trwania animacji", + "animation-rule":"Reguła animacji", + "animation-linear":"Liniowa", + "animation-quad":"Kwadratowa", + "animation-quint":"Kwintowa", + "animation-cycle":"Cykliczna", + "animation-bounce":"Odbijająca się", + "animation-elastic":"Elastyczna", + "animation-dequad":"Odwrócona kwadratowa", + "animation-dequint":"Odwrócona kwintowa", + "animation-decycle":"Odwrócona cykliczna", + "animation-debounce":"Odwrócona odbijająca się", + "animation-delastic":"Odwrócona elastyczna", + "linear-gauge-settings":"Ustawienia miernika liniowego", + "bar-stroke":"Obwódka słupka", + "bar-stroke-width":"Szerokość obwódki słupka", + "bar-stroke-color":"Kolor obwódki słupka", + "bar-background-color":"Kolor tła słupka - początek gradientu", + "bar-background-color-end":"Kolor tła słupka - koniec gradientu", + "progress-bar-color":"Kolor paska postępu", + "progress-bar":"Pasek postępu", + "progress-bar-color-start":"Kolor paska postępu - początek gradientu", + "progress-bar-color-end":"Kolor paska postępu - koniec gradientu", + "major-ticks-names":"Nazwy podziałek głównych", + "show-stroke-ticks":"Pokaż obwódki podziałek", + "major-ticks-font":"Czcionka podziałek głównych", + "border-color":"Kolor obramowania", + "border-width":"Szerokość obramowania", + "needle-circle":"Koło wskazówki", + "needle-circle-color":"Kolor koła wskazówki", + "animation-target":"Cel animacji", + "animation-target-needle":"Wskazówka", + "animation-target-plate":"Tarcza", + "common-settings":"Wspólne ustawienia miernika", + "gauge-type":"Typ miernika", + "gauge-type-arc":"Łuk", + "gauge-type-donut":"Pączek", + "gauge-type-horizontal-bar":"Słupkowy poziomy", + "gauge-type-vertical-bar":"Słupkowy pionowy", + "donut-start-angle":"Kąt początkowy pączka", + "bar-settings":"Ustawienia słupka", + "relative-bar-width":"Względna szerokość słupka", + "neon-glow-brightness":"Jasność efektu świetlnego, (0-100), 0 - wyłączony efekt", + "stripes-thickness":"Grubość pasków, 0 - brak pasków", + "rounded-line-cap":"Użyj zaokrąglonego zakończenia linii", + "bar-color-settings":"Ustawienia koloru słupka", + "use-precise-level-color-values":"Użyj precyzyjnych poziomów kolorów", + "bar-colors":"Kolory słupków, od dolnego do górnego", + "color":"Kolor", + "no-bar-colors":"Brak skonfigurowanych kolorów słupków", + "add-bar-color":"Dodaj kolor słupka", + "from":"Od", + "to":"Do", + "fixed-level-colors":"Kolory słupków z użyciem wartości granicznych", + "gauge-title-settings":"Ustawienia tytułu miernika", + "show-gauge-title":"Pokaż tytuł miernika", + "gauge-title":"Tytuł miernika", + "gauge-title-font":"Czcionka tytułu miernika", + "unit-title-and-timestamp-settings":"Ustawienia jednostki i znacznika czasowego", + "show-timestamp":"Pokaż znacznik czasu wartości", + "timestamp-format":"Format znacznika czasu", + "label-font":"Czcionka etykiety pod wartością", + "value-settings":"Ustawienia wartości", + "show-value":"Pokaż tekst wartości", + "min-max-settings":"Ustawienia etykiet minimalnej/maksymalnej", + "show-min-max":"Pokaż wartości minimalną i maksymalną", + "min-max-font":"Czcionka etykiet minimalnej i maksymalnej", + "show-ticks":"Pokaż podziały", + "tick-width":"Szerokość podziałek", + "tick-color":"Kolor podziałek", + "tick-values":"Wartości podziałek", + "no-tick-values":"Brak skonfigurowanych wartości podziałek", + "add-tick-value":"Dodaj wartość podziałki", + "gauge-appearance":"Wygląd miernika", + "units-title":"Tytuł jednostek", + "value":"Wartość", + "ticks":"Podziały", + "arrow-and-scale-color":"Kolor strzałki i skali domyślnej", + "scale-settings":"Ustawienia skali", + "scale":"Skala", + "scale-color":"Kolory skali", + "compass-appearance":"Wygląd kompasu", + "labels":"Etykiety", + "label-style":"Styl etykiety" + }, + "gpio":{ + "pin":"Pin", + "label":"Label", + "row":"Row", + "column":"Column", + "color":"Color", + "panel-settings":"Panel settings", + "background-color":"Background color", + "gpio-switches":"GPIO switches", + "no-gpio-switches":"No GPIO switches configured", + "add-gpio-switch":"Add GPIO switch", + "gpio-status-request":"GPIO status request", + "method-name":"Method name", + "method-body":"Method body", + "gpio-status-change-request":"GPIO status change request", + "parse-gpio-status-function":"Parse gpio status function", + "gpio-leds":"GPIO leds", + "no-gpio-leds":"No GPIO leds configured", + "add-gpio-led":"Add GPIO led" + }, + "html-card":{ + "html":"HTML", + "css":"CSS" + }, + "input-widgets":{ + "attribute-not-allowed":"Parametr atrybutu nie może być używany w tym widżecie", + "blocked-location":"Geolokalizacja jest zablokowana w twojej przeglądarce", + "claim-device":"Zgłoś urządzenie", + "claim-failed":"Nie udało się zgłosić urządzenia!", + "claim-not-found":"Nie znaleziono urządzenia!", + "claim-successful":"Urządzenie zostało pomyślnie zgłoszone!", + "date":"Data", + "device-name":"Nazwa urządzenia", + "device-name-required":"Wymagana jest nazwa urządzenia", + "discard-changes":"Odrzuć zmiany", + "entity-attribute-required":"Wymagany jest atrybut encji", + "entity-coordinate-required":"Wymagane są obie pola, szerokość geograficzna i długość geograficzna", + "entity-timeseries-required":"Wymagany jest szereg czasowy encji", + "get-location":"Pobierz bieżącą lokalizację", + "invalid-date":"Nieprawidłowa data", + "latitude":"Szerokość geograficzna", + "longitude":"Długość geograficzna", + "min-value-error":"Wartość minimalna to {{value}}", + "max-value-error":"Wartość maksymalna to {{value}}", + "not-allowed-entity":"Wybrana encja nie może mieć współdzielonych atrybutów", + "no-attribute-selected":"Nie wybrano atrybutu", + "no-datakey-selected":"Nie wybrano klucza danych", + "no-coordinate-specified":"Nie określono klucza danych dla szerokości/długości geograficznej", + "no-entity-selected":"Nie wybrano encji", + "no-image":"Brak obrazu", + "no-support-geolocation":"Twoja przeglądarka nie obsługuje geolokalizacji", + "no-support-web-camera":"Twoja przeglądarka nie obsługuje kamer", + "enable-https-use-widget":"Proszę włączyć protokół HTTPS, aby korzystać z tego widżetu", + "no-found-your-camera":"Nie można znaleźć twojej kamery", + "no-permission-camera":"Użytkownik odmówił zgody / Ta witryna nie ma uprawnień do korzystania z kamery", + "no-timeseries-selected":"Nie wybrano szeregu czasowego", + "secret-key":"Tajny klucz", + "secret-key-required":"Wymagany jest tajny klucz", + "switch-attribute-value":"Przełącz wartość atrybutu encji", + "switch-camera":"Przełącz kamerę", + "switch-timeseries-value":"Przełącz wartość szeregu czasowego encji", + "take-photo":"Zrób zdjęcie", + "time":"Czas", + "timeseries-not-allowed":"Parametr szeregu czasowego nie może być używany w tym widżecie", + "update-failed":"Aktualizacja nieudana", + "update-successful":"Aktualizacja udana", + "update-attribute":"Zaktualizuj atrybut", + "update-timeseries":"Zaktualizuj szereg czasowy", + "value":"Wartość", + "general-settings":"Ustawienia ogólne", + "widget-title":"Tytuł widżetu", + "claim-button-label":"Etykieta przycisku zgłaszania", + "show-secret-key-field":"Pokaż pole wprowadzania „Tajny klucz”", + "labels-settings":"Ustawienia etykiet", + "show-labels":"Pokaż etykiety", + "device-name-label":"Etykieta pola wprowadzania nazwy urządzenia", + "secret-key-label":"Etykieta pola wprowadzania tajnego klucza", + "messages-settings":"Ustawienia wiadomości", + "claim-device-success-message":"Komunikat o udanym zgłoszeniu urządzenia", + "claim-device-not-found-message":"Komunikat, gdy urządzenie nie zostało znalezione", + "claim-device-failed-message":"Komunikat o nieudanym zgłoszeniu urządzenia", + "claim-device-name-required-message":"Komunikat błędu „Wymagana nazwa urządzenia”", + "claim-device-secret-key-required-message":"Komunikat błędu „Wymagany tajny klucz”", + "show-label":"Pokaż etykietę", + "label":"Etykieta", + "required":"Wymagane", + "required-error-message":"Komunikat błędu „Wymagane”", + "show-result-message":"Pokaż komunikat z wynikiem", + "integer-field-settings":"Ustawienia pola całkowitego", + "min-value":"Wartość minimalna", + "max-value":"Wartość maksymalna", + "double-field-settings":"Ustawienia pola liczbowego", + "text-field-settings":"Ustawienia pola tekstowego", + "min-length":"Minimalna długość", + "max-length":"Maksymalna długość", + "checkbox-settings":"Ustawienia pola wyboru", + "true-label":"Etykieta zaznaczenia", + "false-label":"Etykieta odznaczenia", + "image-input-settings":"Ustawienia wejścia obrazu", + "display-preview":"Wyświetl podgląd", + "display-clear-button":"Wyświetl przycisk Wyczyść", + "display-apply-button":"Wyświetl przycisk Zastosuj", + "display-discard-button":"Wyświetl przycisk Odrzuć", + "datetime-field-settings":"Ustawienia pola daty/czasu", + "display-time-input":"Wyświetl pole czasowe", + "latitude-key-name":"Nazwa klucza szerokości geograficznej", + "longitude-key-name":"Nazwa klucza długości geograficznej", + "show-get-location-button":"Pokaż przycisk „Pobierz bieżącą lokalizację”", + "use-high-accuracy":"Użyj wysokiej dokładności", + "location-fields-settings":"Ustawienia pól lokalizacji", + "latitude-label":"Etykieta dla szerokości geograficznej", + "longitude-label":"Etykieta dla długości geograficznej", + "input-fields-alignment":"Wyrównanie pól wprowadzania", + "input-fields-alignment-column":"Kolumna (domyślnie)", + "input-fields-alignment-row":"Rząd", + "layout":"Układ", + "row-gap":"Odstęp między rzędami w pikselach", + "column-gap":"Odstęp między kolumnami w pikselach", + "latitude-field-required":"Pole szerokości geograficznej jest wymagane", + "longitude-field-required":"Pole długości geograficznej jest wymagane", + "attribute-settings":"Ustawienia atrybutu", + "widget-mode":"Tryb widżetu", + "widget-mode-update-attribute":"Zaktualizuj atrybut", + "widget-mode-update-timeseries":"Zaktualizuj szereg czasowy", + "attribute-scope":"Zakres atrybutu", + "attribute-scope-server":"Atrybut serwera (domyślnie)", + "attribute-scope-shared":"Współdzielony atrybut", + "value-required":"Wartość wymagana", + "image-settings":"Ustawienia obrazu", + "image-format":"Format obrazu", + "image-format-jpeg":"JPEG", + "image-format-png":"PNG", + "image-format-webp":"WEBP", + "image-quality":"Jakość obrazu stosująca kompresję stratną, np. jpeg i webp", + "max-image-width":"Maksymalna szerokość obrazu", + "max-image-height":"Maksymalna wysokość obrazu", + "action-buttons":"Przyciski akcji", + "show-action-buttons":"Pokaż przyciski akcji", + "update-all-values":"Zaktualizuj wszystkie wartości, nie tylko zmodyfikowane", + "save-button-label":"Etykieta przycisku „ZAPISZ”", + "reset-button-label":"Etykieta przycisku „COFNIJ”", + "group-settings":"Ustawienia grupy", + "show-group-title":"Pokaż tytuł grupy pól związanych z różnymi encjami", + "group-title":"Tytuł grupy", + "fields-alignment":"Wyrównanie pól", + "fields-alignment-row":"Rząd (domyślnie)", + "fields-alignment-column":"Kolumna", + "fields-in-row":"Liczba pól w rzędzie", + "option-value":"Wartość (wpisz „null”, aby utworzyć pustą opcję)", + "option-label":"Etykieta", + "hide-input-field":"Ukryj pole wprowadzania", + "datakey-type":"Typ klucza danych", + "datakey-type-server":"Atrybut serwera (domyślnie)", + "datakey-type-shared":"Współdzielony atrybut", + "datakey-type-timeseries":"Szereg czasowy", + "datakey-value-type":"Typ wartości klucza danych", + "datakey-value-type-string":"Ciąg znaków", + "datakey-value-type-double":"Liczba zmiennoprzecinkowa", + "datakey-value-type-integer":"Liczba całkowita", + "datakey-value-type-json":"JSON", + "datakey-value-type-boolean-checkbox":"Boolean (Pole wyboru)", + "datakey-value-type-boolean-switch":"Boolean (Przełącznik)", + "datakey-value-type-date-time":"Data i godzina", + "datakey-value-type-date":"Data", + "datakey-value-type-time":"Czas", + "datakey-value-type-select":"Wybierz", + "datakey-value-type-color":"Kolor", + "value-is-required":"Wartość jest wymagana", + "ability-to-edit-attribute":"Zdolność do edycji atrybutu", + "ability-to-edit-attribute-editable":"Edytowalny (domyślnie)", + "ability-to-edit-attribute-disabled":"Wyłączony", + "ability-to-edit-attribute-readonly":"Tylko do odczytu", + "disable-on-datakey-name":"Wyłącz na fałszywą wartość innego klucza danych (podaj nazwę klucza danych)", + "field-appearance":"Wygląd pola", + "appearance-fill":"Wypełnienie", + "appearance-outline":"Obrys", + "subscript-sizing":"Rozmiar indeksu dolnego", + "subscript-sizing-fixed":"Stały", + "subscript-sizing-dynamic":"Dynamiczny", + "slide-toggle-settings":"Ustawienia przełącznika", + "slide-toggle-label-position":"Pozycja etykiety przełącznika", + "slide-toggle-label-position-after":"Po", + "slide-toggle-label-position-before":"Przed", + "select-options":"Opcje wyboru", + "no-select-options":"Brak skonfigurowanych opcji wyboru", + "add-select-option":"Dodaj opcję wyboru", + "numeric-field-settings":"Ustawienia pola numerycznego", + "step-interval":"Interwał kroku pomiędzy wartościami", + "error-messages":"Komunikaty o błędach", + "min-value-error-message":"Komunikat o błędzie 'Wartość minimalna'", + "max-value-error-message":"Komunikat o błędzie 'Wartość maksymalna'", + "invalid-date-error-message":"Komunikat o błędzie 'Nieprawidłowa data'", + "invalid-JSON-error-message":"Komunikat o błędzie 'Nieprawidłowy JSON'", + "icon-settings":"Ustawienia ikony", + "dialog-editor-settings":"Ustawienia edytora dialogu", + "use-custom-icon":"Użyj niestandardowej ikony", + "input-cell-icon":"Ikona do wyświetlenia przed komórką wejściową", + "value-conversion-settings":"Ustawienia konwersji wartości", + "get-value-settings":"Ustawienia pobierania wartości", + "use-get-value-function":"Użyj funkcji getValue", + "get-value-function":"Funkcja getValue", + "set-value-settings":"Ustawienia ustawiania wartości", + "use-set-value-function":"Użyj funkcji setValue", + "set-value-function":"Funkcja setValue", + "json-invalid":"Wartość JSON ma nieprawidłowy format", + "title":"Tytuł", + "cancel-button-label":"Etykieta przycisku 'Anuluj'" + }, + "invalid-qr-code-text":"Nieprawidłowy tekst wejściowy dla kodu QR. Wejście powinno być typu string.", + "qr-code":{ + "use-qr-code-text-function":"Użyj funkcji tekstowej kodu QR", + "qr-code-text-pattern":"Wzór tekstu kodu QR (np. '${entityName} | ${keyName} - jakiś tekst.')", + "qr-code-text-pattern-hint":"Wzór tekstu kodu QR używa wartości pierwszego znalezionego klucza w encjach w aliasie encji.", + "qr-code-text-pattern-required":"Wzór tekstu kodu QR jest wymagany.", + "qr-code-text-function":"Funkcja tekstu kodu QR" + }, + "label-widget":{ + "label-pattern":"Wzór", + "label-pattern-hint":"Wskazówka: na przykład 'Tekst ${keyName} jednostki.' lub ${#<index klucza>} jednostki'", + "label-pattern-required":"Wzór jest wymagany", + "label-position":"Położenie (Procentowo w stosunku do tła)", + "x-pos":"X", + "y-pos":"Y", + "background-color":"Kolor tła", + "font-settings":"Ustawienia czcionki", + "background-image":"Obraz tła", + "labels":"Etykiety", + "no-labels":"Brak skonfigurowanych etykiet", + "add-label":"Dodaj etykietę" + }, + "navigation":{ + "title":"Tytuł", + "navigation-path":"Ścieżka nawigacji", + "filter-type":"Typ filtru", + "filter-type-all":"Wszystkie elementy", + "filter-type-include":"Zawiera elementy", + "filter-type-exclude":"Wyklucza elementy", + "items":"Elementy", + "enter-urls-to-filter":"Wprowadź adresy URL do filtrowania..." + }, + "persistent-table":{ + "rpc-id":"ID RPC", + "message-type":"Typ wiadomości", + "method":"Metoda", + "params":"Parametry", + "created-time":"Czas utworzenia", + "expiration-time":"Czas wygaśnięcia", + "retries":"Próby", + "status":"Status", + "filter":"Filtr", + "refresh":"Odśwież", + "add":"Dodaj żądanie RPC", + "details":"Szczegóły", + "delete":"Usuń", + "delete-request-title":"Usuń trwałe żądanie RPC", + "delete-request-text":"Czy na pewno chcesz usunąć to żądanie?", + "details-title":"Szczegóły ID RPC: ", + "additional-info":"Dodatkowe informacje", + "response":"Odpowiedź", + "any-status":"Dowolny status", + "rpc-status-list":"Lista statusów RPC", + "no-request-prompt":"Brak żądań do wyświetlenia", + "send-request":"Wyślij żądanie", + "add-title":"Utwórz trwałe żądanie RPC", + "method-error":"Wymagana jest metoda.", + "timeout-error":"Minimalna wartość czasu oczekiwania to 5000 (5 sekund).", + "white-space-error":"Biała przestrzeń nie jest dozwolona.", + "rpc-status":{ + "QUEUED":"W KOLEJCE", + "SENT":"WYSŁANE", + "DELIVERED":"DOSTARCZONE", + "SUCCESSFUL":"UDANE", + "TIMEOUT":"PRZEKROCZONY LIMIT CZASU", + "EXPIRED":"WYGASŁE", + "FAILED":"NIEUDANE" + }, + "rpc-search-status-all":"WSZYSTKO", + "message-types":{ + "false":"Dwukierunkowe", + "true":"Jednokierunkowe" + }, + "general-settings":"Ustawienia ogólne", + "enable-filter":"Włącz filtr", + "enable-sticky-header":"Wyświetl nagłówek podczas przewijania", + "enable-sticky-action":"Wyświetl kolumnę akcji podczas przewijania", + "display-request-details":"Wyświetl szczegóły żądania", + "allow-send-request":"Zezwalaj na wysyłanie żądań RPC", + "allow-delete-request":"Zezwalaj na usuwanie żądań", + "columns-settings":"Ustawienia kolumn", + "display-columns":"Wyświetlane kolumny", + "column":"Kolumna", + "no-columns-found":"Nie znaleziono kolumn", + "no-columns-matching":"'{{column}}' nie znaleziono." + }, + "range-chart":{ + "chart":"Wykres", + "data-zoom":"Powiększenie danych", + "range-colors":"Kolory zakresu", + "out-of-range-color":"Kolor poza zakresem", + "fill-area":"Wypełnij obszar", + "range-chart-card-style":"Styl karty wykresu zakresu" + }, + "rpc":{ + "value-settings":"Ustawienia wartości", + "initial-value":"Początkowa wartość", + "retrieve-value-settings":"Ustawienia pobierania włączonej/wyłączonej wartości", + "retrieve-value-method":"Pobierz wartość za pomocą metody", + "retrieve-value-method-none":"Nie pobieraj", + "retrieve-value-method-rpc":"Wywołaj metodę pobierania wartości RPC", + "retrieve-value-method-attribute":"Subskrybuj atrybut", + "retrieve-value-method-timeseries":"Subskrybuj szereg czasowy", + "attribute-value-key":"Klucz atrybutu", + "timeseries-value-key":"Klucz szeregu czasowego", + "get-value-method":"Metoda RPC pobierz wartość", + "parse-value-function":"Funkcja analizy wartości", + "update-value-settings":"Ustawienia aktualizacji wartości", + "set-value-method":"Metoda RPC ustaw wartość", + "convert-value-function":"Funkcja konwersji wartości", + "rpc-settings":"Ustawienia RPC", + "request-timeout":"Limit czasu żądania RPC (ms)", + "persistent-rpc-settings":"Ustawienia trwałego RPC", + "request-persistent":"Trwałe żądanie RPC", + "persistent-polling-interval":"Interwał odpytywania (ms) w celu uzyskania odpowiedzi na trwałe polecenie RPC", + "common-settings":"Wspólne ustawienia", + "switch-title":"Tytuł przełącznika", + "show-on-off-labels":"Pokaż etykiety włącz/wyłącz", + "slide-toggle-label":"Etykieta przycisku przełącznika", + "label-position":"Pozycja etykiety", + "label-position-before":"Przed", + "label-position-after":"Po", + "slider-color":"Kolor suwaka", + "slider-color-primary":"Podstawowy", + "slider-color-accent":"Akcent", + "slider-color-warn":"Ostrzeżenie", + "button-style":"Styl przycisku", + "button-raised":"Podniesiony przycisk", + "button-primary":"Podstawowy kolor", + "button-background-color":"Kolor tła przycisku", + "button-text-color":"Kolor tekstu przycisku", + "widget-title":"Tytuł widżetu", + "button-label":"Etykieta przycisku", + "device-attribute-scope":"Zakres atrybutu urządzenia", + "server-attribute":"Atrybut serwera", + "shared-attribute":"Atrybut wspólny", + "device-attribute-parameters":"Parametry atrybutu urządzenia", + "is-one-way-command":"Jest to polecenie jednokierunkowe", + "rpc-method":"Metoda RPC", + "rpc-method-params":"Parametry metody RPC", + "show-rpc-error":"Pokaż błąd wykonania polecenia RPC", + "led-title":"Tytuł diody LED", + "led-color":"Kolor diody LED", + "check-status-settings":"Ustawienia sprawdzania statusu", + "perform-rpc-status-check":"Wykonaj sprawdzanie statusu urządzenia RPC", + "retrieve-led-status-value-method":"Pobierz stan diody LED za pomocą metody", + "led-status-value-attribute":"Atrybut urządzenia zawierający stan diody LED", + "led-status-value-timeseries":"Szereg czasowy urządzenia zawierający stan diody LED", + "check-status-method":"Metoda sprawdzania statusu urządzenia RPC", + "parse-led-status-value-function":"Funkcja analizy stanu diody LED", + "knob-title":"Tytuł pokrętła", + "min-value":"Minimalna wartość", + "max-value":"Maksymalna wartość" + }, + "maps":{ + "select-entity":"Wybierz jednostkę", + "select-entity-hint":"Wskazówka: po wybraniu kliknij na mapie, aby ustawić pozycję", + "tooltips":{ + "placeMarker":"Kliknij, aby umieścić jednostkę '{{entityName}}'", + "firstVertex":"Wielokąt dla '{{entityName}}': kliknij, aby umieścić pierwszy punkt", + "firstVertex-cut":"Kliknij, aby umieścić pierwszy punkt", + "continueLine":"Wielokąt dla '{{entityName}}': kliknij, aby kontynuować rysowanie", + "continueLine-cut":"Kliknij, aby kontynuować rysowanie", + "finishLine":"Kliknij na istniejący znacznik, aby zakończyć", + "finishPoly":"Wielokąt dla '{{entityName}}': kliknij na pierwszy znacznik, aby zakończyć i zapisać", + "finishPoly-cut":"Kliknij na pierwszy znacznik, aby zakończyć i zapisać", + "finishRect":"Wielokąt dla '{{entityName}}': kliknij, aby zakończyć i zapisać", + "startCircle":"Okrąg dla '{{entityName}}': kliknij, aby umieścić środek okręgu", + "finishCircle":"Okrąg dla '{{entityName}}': kliknij, aby zakończyć okrąg", + "placeCircleMarker":"Kliknij, aby umieścić znacznik okręgu" + }, + "actions":{ + "finish":"Zakończ", + "cancel":"Anuluj", + "removeLastVertex":"Usuń ostatni punkt" + }, + "buttonTitles":{ + "drawMarkerButton":"Umieść obiekt", + "drawPolyButton":"Utwórz wielokąt", + "drawLineButton":"Utwórz linię", + "drawCircleButton":"Utwórz okrąg", + "drawRectButton":"Utwórz prostokąt", + "editButton":"Tryb edycji", + "dragButton":"Tryb przeciągania", + "cutButton":"Odcinanie obszaru wielokąta", + "deleteButton":"Usuń", + "drawCircleMarkerButton":"Utwórz znacznik okręgu", + "rotateButton":"Obróć wielokąt" + }, + "map-provider-settings":"Ustawienia dostawcy map", + "map-provider":"Dostawca map", + "map-provider-google":"Google Maps", + "map-provider-openstreet":"OpenStreet Maps", + "map-provider-here":"HERE Maps", + "map-provider-image":"Mapa obrazu", + "map-provider-tencent":"Tencent Maps", + "openstreet-provider":"Dostawca mapy OpenStreet", + "openstreet-provider-mapnik":"OpenStreetMap.Mapnik (Domyślny)", + "openstreet-provider-hot":"OpenStreetMap.HOT", + "openstreet-provider-esri-street":"Esri.WorldStreetMap", + "openstreet-provider-esri-topo":"Esri.WorldTopoMap", + "openstreet-provider-esri-imagery":"Esri.WorldImagery", + "openstreet-provider-cartodb-positron":"CartoDB.Positron", + "openstreet-provider-cartodb-dark-matter":"CartoDB.DarkMatter", + "use-custom-provider":"Użyj dostawcy niestandardowego", + "custom-provider-tile-url":"Niestandardowy adres URL kafelka dostawcy", + "google-maps-api-key":"Klucz API Google Maps", + "default-map-type":"Domyślny rodzaj mapy", + "google-map-type-roadmap":"Mapa drogowa", + "google-map-type-satelite":"Satelita", + "google-map-type-hybrid":"Hybrydowa", + "google-map-type-terrain":"Tereno", + "map-layer":"Warstwa mapy", + "here-map-normal-day":"HERE.normalDay (Domyślna)", + "here-map-normal-night":"HERE.normalNight", + "here-map-hybrid-day":"HERE.hybridDay", + "here-map-terrain-day":"HERE.terrainDay", + "credentials":"Poświadczenia", + "here-app-id":"ID aplikacji HERE", + "here-app-code":"Kod aplikacji HERE", + "here-api-key":"Klucz API HERE", + "here-use-new-version-api-3":"Użyj wersji API 3", + "tencent-maps-api-key":"Klucz API Tencent Maps", + "tencent-map-type-roadmap":"Mapa drogowa", + "tencent-map-type-satelite":"Satelita", + "tencent-map-type-hybrid":"Hybrydowa", + "image-map-background":"Tło mapy obrazu", + "image-map-background-from-entity-attribute":"Użyj tła mapy obrazu z atrybutu encji", + "image-url-source-entity-alias":"Źródło URL obrazu z aliasu encji", + "image-url-source-entity-attribute":"Źródło URL obrazu z atrybutu encji", + "common-map-settings":"Wspólne ustawienia mapy", + "x-pos-key-name":"Nazwa klucza X", + "y-pos-key-name":"Nazwa klucza Y", + "latitude-key-name":"Nazwa klucza szerokości geograficznej", + "longitude-key-name":"Nazwa klucza długości geograficznej", + "default-map-zoom-level":"Domyślny poziom przybliżenia mapy (0 - 20)", + "default-map-center-position":"Domyślna pozycja centralna mapy (0,0)", + "disable-scroll-zooming":"Wyłącz przybliżanie za pomocą przewijania", + "disable-double-click-zooming":"Wyłącz przybliżanie podwójnym kliknięciem", + "disable-zoom-control-buttons":"Wyłącz przyciski sterowania przybliżeniem", + "fit-map-bounds":"Dopasuj granice mapy, aby pokryć wszystkie markery", + "use-default-map-center-position":"Użyj domyślnej pozycji centralnej mapy", + "entities-limit":"Limit encji do wczytania", + "markers-settings":"Ustawienia znaczników", + "marker-offset-x":"Przesunięcie X znacznika względem pozycji pomnożone przez szerokość znacznika", + "marker-offset-y":"Przesunięcie Y znacznika względem pozycji pomnożone przez wysokość znacznika", + "position-function":"Funkcja konwersji pozycji, powinna zwracać współrzędne x, y jako liczby z zakresu od 0 do 1", + "draggable-marker":"Znacznik do przeciągania", + "label":"Etykieta", + "show-label":"Pokaż etykietę", + "use-label-function":"Użyj funkcji etykiety", + "label-pattern":"Etykieta (przykłady wzorców: '${entityName}', '${entityName}: (Tekst ${keyName} jednostki.)' )", + "label-function":"Funkcja etykiety", + "tooltip":"Podpowiedź", + "show-tooltip":"Pokaż podpowiedź", + "show-tooltip-action":"Działanie wyświetlania podpowiedzi", + "show-tooltip-action-click":"Pokaż podpowiedź po kliknięciu (Domyślnie)", + "show-tooltip-action-hover":"Pokaż podpowiedź po najechaniu", + "auto-close-tooltips":"Automatyczne zamykanie podpowiedzi", + "use-tooltip-function":"Użyj funkcji podpowiedzi", + "tooltip-pattern":"Podpowiedź (np. 'Tekst ${keyName} jednostki.' lub Tekst linku')", + "tooltip-function":"Funkcja podpowiedzi", + "tooltip-offset-x":"Przesunięcie X podpowiedzi względem kotwicy znacznika pomnożone przez szerokość znacznika", + "tooltip-offset-y":"Przesunięcie Y podpowiedzi względem kotwicy znacznika pomnożone przez wysokość znacznika", + "color":"Kolor", + "use-color-function":"Użyj funkcji koloru", + "color-function":"Funkcja koloru", + "marker-image":"Obraz znacznika", + "use-marker-image-function":"Użyj funkcji obrazu znacznika", + "custom-marker-image":"Niestandardowy obraz znacznika", + "custom-marker-image-size":"Niestandardowy rozmiar obrazu znacznika (px)", + "marker-image-function":"Funkcja obrazu znacznika", + "marker-images":"Obrazy znaczników", + "polygon-settings":"Ustawienia wielokąta", + "show-polygon":"Pokaż wielokąt", + "polygon-key-name":"Nazwa klucza wielokąta", + "enable-polygon-edit":"Włącz edycję wielokąta", + "polygon-label":"Etykieta wielokąta", + "show-polygon-label":"Pokaż etykietę wielokąta", + "use-polygon-label-function":"Użyj funkcji etykiety wielokąta", + "polygon-label-pattern":"Etykieta wielokąta (przykłady wzorców: '${entityName}', '${entityName}: (Tekst ${keyName} jednostki.)' )", + "polygon-label-function":"Funkcja etykiety wielokąta", + "polygon-tooltip":"Podpowiedź wielokąta", + "show-polygon-tooltip":"Pokaż podpowiedź wielokąta", + "auto-close-polygon-tooltips":"Automatyczne zamykanie podpowiedzi wielokąta", + "use-polygon-tooltip-function":"Użyj funkcji podpowiedzi wielokąta", + "polygon-tooltip-pattern":"Podpowiedź (np. 'Tekst ${keyName} jednostki.' lub Tekst linku')", + "polygon-tooltip-function":"Funkcja podpowiedzi wielokąta", + "polygon-color":"Kolor wielokąta", + "polygon-opacity":"Przezroczystość wielokąta", + "use-polygon-color-function":"Użyj funkcji koloru wielokąta", + "polygon-color-function":"Funkcja koloru wielokąta", + "polygon-stroke":"Obrys wielokąta", + "stroke-color":"Kolor obrysu", + "stroke-opacity":"Przezroczystość obrysu", + "stroke-weight":"Grubość obrysu", + "use-polygon-stroke-color-function":"Użyj funkcji koloru obrysu wielokąta", + "polygon-stroke-color-function":"Funkcja koloru obrysu wielokąta", + "circle-settings":"Ustawienia okręgu", + "show-circle":"Pokaż okrąg", + "circle-key-name":"Nazwa klucza okręgu", + "enable-circle-edit":"Włącz edycję okręgu", + "circle-label":"Etykieta okręgu", + "show-circle-label":"Pokaż etykietę okręgu", + "use-circle-label-function":"Użyj funkcji etykiety okręgu", + "circle-label-pattern":"Etykieta okręgu (przykłady wzorców: '${entityName}', '${entityName}: (Tekst ${keyName} jednostki.)' )", + "circle-label-function":"Funkcja etykiety okręgu", + "circle-tooltip":"Podpowiedź okręgu", + "show-circle-tooltip":"Pokaż podpowiedź okręgu", + "auto-close-circle-tooltips":"Automatyczne zamykanie podpowiedzi okręgu", + "use-circle-tooltip-function":"Użyj funkcji podpowiedzi okręgu", + "circle-tooltip-pattern":"Podpowiedź (np. 'Tekst ${keyName} jednostki.' lub Tekst linku')", + "circle-tooltip-function":"Funkcja podpowiedzi okręgu", + "circle-fill-color":"Kolor wypełnienia okręgu", + "circle-fill-color-opacity":"Przezroczystość koloru wypełnienia okręgu", + "use-circle-fill-color-function":"Użyj funkcji koloru wypełnienia okręgu", + "circle-fill-color-function":"Funkcja koloru wypełnienia okręgu", + "circle-stroke":"Obrys okręgu", + "use-circle-stroke-color-function":"Użyj funkcji koloru obrysu okręgu", + "circle-stroke-color-function":"Funkcja koloru obrysu okręgu", + "markers-clustering-settings":"Ustawienia grupowania znaczników", + "use-map-markers-clustering":"Użyj grupowania znaczników na mapie", + "zoom-on-cluster-click":"Powiększ po kliknięciu na klastrze", + "max-cluster-zoom":"Maksymalny poziom powiększenia, przy którym znacznik może być częścią klastra (0 - 18)", + "max-cluster-radius-pixels":"Maksymalny promień, który może obejmować klaster w pikselach", + "cluster-zoom-animation":"Pokaż animację znaczników podczas przybliżania", + "show-markers-bounds-on-cluster-mouse-over":"Pokaż granice znaczników po najechaniu myszą na klaster", + "spiderfy-max-zoom-level":"Rozpoznaj na najwyższym poziomie powiększenia (aby zobaczyć wszystkie znaczniki klastra)", + "load-optimization":"Optymalizacja ładowania", + "cluster-chunked-loading":"Użyj fragmentów do dodawania znaczników, aby strona się nie zawiesiła", + "cluster-markers-lazy-load":"Użyj ładowania opóźnionego do dodawania znaczników", + "editor-settings":"Ustawienia edytora", + "enable-snapping":"Włącz przyciąganie do innych wierzchołków dla precyzyjnego rysowania", + "init-draggable-mode":"Inicjalizuj mapę w trybie przeciągania", + "hide-all-edit-buttons":"Ukryj wszystkie przyciski edycji", + "hide-draw-buttons":"Ukryj przyciski rysowania", + "hide-edit-buttons":"Ukryj przyciski edycji", + "hide-remove-button":"Ukryj przycisk usuwania", + "route-map-settings":"Ustawienia mapy trasy", + "trip-animation-settings":"Ustawienia animacji podróży", + "normalization-step":"Krok normalizacji danych (ms)", + "tooltip-background-color":"Kolor tła podpowiedzi", + "tooltip-font-color":"Kolor czcionki podpowiedzi", + "tooltip-opacity":"Przezroczystość podpowiedzi (0-1)", + "auto-close-tooltip":"Automatyczne zamykanie podpowiedzi", + "rotation-angle":"Ustaw dodatkowy kąt obrotu dla znacznika (stopnie)", + "path-settings":"Ustawienia ścieżki", + "path-color":"Kolor ścieżki", + "use-path-color-function":"Użyj funkcji koloru ścieżki", + "path-color-function":"Funkcja koloru ścieżki", + "path-decorator":"Dekorator ścieżki", + "use-path-decorator":"Użyj dekoratora ścieżki", + "decorator-symbol":"Symbol dekoratora", + "decorator-symbol-arrow-head":"Strzała", + "decorator-symbol-dash":"Kreska", + "decorator-symbol-size":"Rozmiar symbolu dekoratora (px)", + "use-path-decorator-custom-color":"Użyj niestandardowego koloru dekoratora ścieżki", + "decorator-custom-color":"Niestandardowy kolor dekoratora", + "decorator-offset":"Przesunięcie dekoratora", + "end-decorator-offset":"Przesunięcie końcowe dekoratora", + "decorator-repeat":"Powtórzenie dekoratora", + "points-settings":"Ustawienia punktów", + "show-points":"Pokaż punkty", + "point-color":"Kolor punktu", + "point-size":"Rozmiar punktu (px)", + "use-point-color-function":"Użyj funkcji koloru punktu", + "point-color-function":"Funkcja koloru punktu", + "use-point-as-anchor":"Użyj punktu jako kotwicy", + "point-as-anchor-function":"Funkcja punktu jako kotwicy", + "independent-point-tooltip":"Niezależna podpowiedź punktu", + "clustering-markers":"Klastrowanie znaczników", + "use-icon-create-function":"Użyj funkcji koloru znaczników", + "marker-color-function":"Funkcja koloru znacznika" + }, + "markdown":{ + "use-markdown-text-function":"Użyj funkcji wartości markdown/HTML", + "markdown-text-function":"Funkcja wartości markdown/HTML", + "markdown-text-pattern":"Wzorzec markdown/HTML (markdown lub HTML z zmiennymi, np. '${entityName} lub ${keyName} - jakiś tekst.')", + "apply-default-markdown-style":"Zastosuj domyślny styl markdown", + "markdown-css":"CSS markdown/HTML" + }, + "simple-card":{ + "label":"Etykieta", + "label-position":"Pozycja etykiety", + "label-position-left":"Po lewej", + "label-position-top":"Na górze" + }, + "value-card":{ + "layout":"Układ", + "layout-square":"Kwadratowy", + "layout-vertical":"Pionowy", + "layout-centered":"Wyśrodkowany", + "layout-simplified":"Uproszczony", + "layout-horizontal":"Poziomy", + "layout-horizontal-reversed":"Odwrócony poziomy", + "label":"Etykieta", + "icon":"Ikona", + "value":"Wartość", + "date":"Data", + "value-card-style":"Styl karty wartości", + "auto-scale":"Automatyczne dostosowanie skali" + }, + "liquid-level-card":{ + "layout-simple":"Prosty", + "layout-percentage":"Procentowy", + "layout-absolute":"Bezwzględny", + "layout":"Układ", + "background-overlay":"Nakładka na tło wartości", + "total-volume":"Całkowita objętość", + "tank":"Zbiornik", + "shape":"Kształt", + "datasource-units":"Jednostki źródła danych", + "widget-units":"Jednostki widżetu", + "decimals":"Miejsca dziesiętne", + "liquid":"Ciecz", + "liquid-color":"Kolor cieczy", + "value":"Wartość", + "value-font":"Czcionka wartości", + "level":"Poziom", + "last-update":"Ostatnia aktualizacja", + "shape-by-attribute":"Ustaw kształt zbiornika według nazwy atrybutu", + "tooltip-background":"Kolor tła", + "background-blur":"Rozmycie tła", + "tank-color":"Kolor zbiornika", + "static":"Statyczny", + "see-examples":"Zobacz przykłady", + "attribute":"Atrybut", + "shape-type":"Typ", + "v-oval":"Pionowy Owal", + "v-cylinder":"Pionowy Cylinder", + "v-capsule":"Pionowa Kapsuła", + "rectangle":"Prostokąt", + "h-oval":"Poziomy Owal", + "h-ellipse":"Pozioma Elipsa", + "h-dish-ends":"Poziome Zakończenia Dzbanka", + "h-cylinder":"Poziomy Cylinder", + "h-capsule":"Pozioma Kapsuła", + "h-elliptical_2_1":"Pozioma Elipsa 2:1", + "icon":"Ikona karty", + "title":"Tytuł karty", + "units":"Jednostki", + "color-and-font":"Kolor i czcionka", + "shape-attribute-name":"Nazwa atrybutu", + "total-volume-required":"Wymagana jest całkowita objętość.", + "attribute-name-required":"Wymagana jest nazwa atrybutu.", + "attribute-key-not-set":"Klucz atrybutu '{{attributeName}}' nie jest ustawiony", + "attribute-key-invalid":"Klucz atrybutu '{{attributeName}}' jest nieprawidłowy" + }, + "aggregated-value-card":{ + "subtitle":"Podtytuł", + "chart":"Wykres", + "values":"Wartości", + "value-appearance":"Wygląd wartości", + "position":"Pozycja", + "position-center":"Środek", + "position-right-top":"Prawy górny róg", + "position-right-bottom":"Prawy dolny róg", + "position-left-top":"Lewy górny róg", + "position-left-bottom":"Lewy dolny róg", + "font":"Czcionka", + "color":"Kolor", + "arrow":"Strzałka", + "display-up-down-arrow":"Wyświetl strzałkę w górę/dół", + "add-value":"Dodaj wartość", + "remove-value":"Usuń wartość", + "no-values":"Brak skonfigurowanych wartości", + "aggregation":"Agregacja", + "aggregated-value-card-style":"Styl karty zagregowanej wartości", + "auto-scale":"Automatyczne skalowanie" + }, + "value-chart-card":{ + "layout":"Układ", + "layout-left":"Lewo", + "layout-right":"Prawo", + "auto-scale":"Automatyczna skala", + "icon":"Ikona", + "value":"Wartość", + "chart":"Wykres", + "value-chart-card-style":"Styl karty wartości z wykresem" + }, + "progress-bar":{ + "layout":"Układ", + "layout-default":"Domyślny", + "layout-simplified":"Uproszczony", + "auto-scale":"Automatyczna skala", + "icon":"Ikona", + "value":"Wartość", + "range":"Zakres", + "min":"min", + "max":"max", + "range-ticks":"Podziałka zakresu", + "bar":"Pasek", + "bar-color":"Kolor paska", + "bar-background":"Tło paska", + "progress-bar-card-style":"Styl karty paska postępu" + }, + "alarm-count":{ + "alarm-count-card-style":"Styl karty licznika alarmów" + }, + "entity-count":{ + "entity-count-card-style":"Entity count card style" + }, + "count":{ + "layout":"Układ", + "layout-column":"Kolumna", + "layout-row":"Wiersz", + "label":"Etykieta", + "icon":"Ikona", + "icon-background":"Tło ikony", + "value":"Wartość", + "chevron":"Strzałka", + "auto-scale":"Automatyczna skala" + }, + "table":{ + "common-table-settings":"Wspólne ustawienia tabeli", + "enable-search":"Włącz wyszukiwanie", + "enable-sticky-header":"Zawsze wyświetlaj nagłówek", + "enable-sticky-action":"Zawsze wyświetlaj kolumnę akcji", + "hidden-cell-button-display-mode":"Tryb wyświetlania ukrytego przycisku akcji w komórce", + "show-empty-space-hidden-action":"Pokaż pustą przestrzeń zamiast ukrytego przycisku akcji w komórce", + "dont-reserve-space-hidden-action":"Nie rezerwuj miejsca na ukryte przyciski akcji", + "display-timestamp":"Znak czasu", + "display-pagination":"Wyświetl paginację", + "default-page-size":"Domyślny rozmiar strony", + "use-entity-label-tab-name":"Użyj etykiety encji w nazwie karty", + "hide-empty-lines":"Ukryj puste wiersze", + "row-style":"Styl wiersza", + "use-row-style-function":"Użyj funkcji stylu wiersza", + "row-style-function":"Funkcja stylu wiersza", + "cell-style":"Styl komórki", + "use-cell-style-function":"Użyj funkcji stylu komórki", + "cell-style-function":"Funkcja stylu komórki", + "cell-content":"Zawartość komórki", + "use-cell-content-function":"Użyj funkcji zawartości komórki", + "cell-content-function":"Funkcja zawartości komórki", + "show-latest-data-column":"Pokaż kolumnę z najnowszymi danymi", + "latest-data-column-order":"Kolejność kolumny z najnowszymi danymi", + "entities-table-title":"Tytuł tabeli encji", + "enable-select-column-display":"Włącz wybór kolumn do wyświetlenia", + "display-entity-name":"Wyświetl kolumnę z nazwą encji", + "entity-name-column-title":"Tytuł kolumny z nazwą encji", + "display-entity-label":"Wyświetl kolumnę z etykietą encji", + "entity-label-column-title":"Tytuł kolumny z etykietą encji", + "display-entity-type":"Wyświetl kolumnę z typem encji", + "default-sort-order":"Domyślna kolejność sortowania", + "custom-title":"Niestandardowy tytuł nagłówka", + "column-width":"Szerokość kolumny (px lub %)", + "default-column-visibility":"Domyślna widoczność kolumny", + "column-visibility-visible":"Widoczna", + "column-visibility-hidden":"Ukryta", + "column-visibility-hidden-mobile":"Ukryta w trybie mobilnym", + "column-selection-to-display":"Wybór kolumn w 'Kolumny do wyświetlenia'", + "column-selection-to-display-enabled":"Włączony", + "column-selection-to-display-disabled":"Wyłączony", + "alarms-table-title":"Tytuł tabeli alarmów", + "enable-alarms-selection":"Włącz wybór alarmów", + "enable-alarms-search":"Włącz wyszukiwanie alarmów", + "enable-alarm-filter":"Włącz filtr alarmów", + "display-alarm-details":"Wyświetl szczegóły alarmu", + "allow-alarms-ack":"Zezwalaj na potwierdzanie alarmów", + "allow-alarms-clear":"Zezwalaj na usuwanie alarmów", + "display-alarm-activity":"Wyświetl aktywność alarmu", + "allow-alarms-assign":"Zezwalaj na przypisywanie alarmów", + "columns":"Kolumny", + "column-settings":"Ustawienia kolumn", + "remove-column":"Usuń kolumnę", + "add-column":"Dodaj kolumnę", + "no-columns":"Brak skonfigurowanych kolumn", + "columns-to-display":"Kolumny do wyświetlenia", + "table-header":"Nagłówek tabeli", + "header-buttons":"Przyciski nagłówka", + "table-buttons":"Przyciski tabeli", + "pagination":"Paginacja", + "rows":"Wiersze", + "timeseries-column-error":"Należy określić co najmniej jedną kolumnę szeregów czasowych", + "alarm-column-error":"Należy określić co najmniej jedną kolumnę alarmów", + "table-tabs":"Karty tabeli", + "show-cell-actions-menu-mobile":"Pokaż menu rozwijane akcji komórek w trybie mobilnym" + }, + "wind-speed-direction":{ + "layout":"Układ", + "layout-default":"Domyślny", + "layout-advanced":"Zaawansowany", + "layout-simplified":"Uproszczony", + "values":"Wartości", + "wind-direction":"Kierunek wiatru", + "center-value":"Wartość środkowa", + "icon":"Ikona", + "arrow":"Strzałka", + "ticks":"Kreski", + "labels-type":"Typ etykiet", + "directional-names":"Nazwy kierunkowe", + "degrees":"Stopnie", + "major-ticks":"Główne kreski", + "minor-ticks":"Drobne kreski", + "wind-speed-direction-card-style":"Styl karty prędkości i kierunku wiatru", + "ticks-color":"Kolor kresek", + "ticks-labels-type":"Typ etykiet kreskowych", + "arrow-color":"Kolor strzałki" + }, + "value-source":{ + "value-source":"Źródło wartości", + "predefined-value":"Wartość predefiniowana", + "entity-attribute":"Atrybut jednostki", + "value":"Wartość", + "source-entity-alias":"Alias jednostki źródłowej", + "source-entity-attribute":"Atrybut jednostki źródłowej" + }, + "widget-font":{ + "font-settings":"Ustawienia czcionki", + "font-family":"Rodzina czcionek", + "size":"Rozmiar", + "relative-font-size":"Względny rozmiar czcionki (procenty)", + "font-style":"Styl", + "font-style-normal":"Normalny", + "font-style-italic":"Kursywa", + "font-style-oblique":"Pochyły", + "font-weight":"Grubość", + "font-weight-normal":"Normalna", + "font-weight-bold":"Pogrubiona", + "font-weight-bolder":"Grubsza", + "font-weight-lighter":"Lżejsza", + "color":"Kolor", + "shadow-color":"Kolor cienia", + "preview":"Podgląd", + "line-height":"Wysokość wiersza", + "auto":"Automatyczne" + }, + "home":{ + "no-data-available":"Brak dostępnych danych" + }, + "system-info":{ + "cpu":"Procesor", + "ram":"RAM", + "disk":"Dysk", + "cpu-warning-text":"Wysokie zużycie procesora. Aby uniknąć awarii systemu, zoptymalizuj wydajność systemu.", + "cpu-critical-text":"Krytycznie wysokie zużycie procesora. Aby uniknąć awarii systemu, zoptymalizuj wydajność systemu.", + "ram-warning-text":"Niski zapas pamięci RAM. Aby uniknąć awarii systemu, zoptymalizuj wydajność systemu lub zwiększ rozmiar pamięci RAM.", + "ram-critical-text":"Krytycznie niski zapas pamięci RAM. Aby uniknąć awarii systemu, zoptymalizuj wydajność systemu lub zwiększ rozmiar pamięci RAM.", + "disk-warning-text":"Niskie miejsce na dysku. Aby uniknąć utraty danych, zwolnij miejsce lub zwiększ rozmiar dysku.", + "disk-critical-text":"Krytycznie niskie miejsce na dysku. Aby uniknąć utraty danych, zwolnij miejsce lub zwiększ rozmiar dysku." + }, + "cluster-info":{ + "service-id":"ID usługi", + "service-type":"Typ usługi", + "no-data":"Brak danych" + }, + "transport-messages":{ + "title":"Wiadomości transportowe", + "info":"Wszystkie wiadomości odebrane od urządzeń" + }, + "activity":{ + "title":"Aktywność" + }, + "documentation":{ + "title":"Dokumentacja", + "add-link":"Dodaj link", + "add-link-title":"Dodaj link do dokumentacji", + "name":"Nazwa", + "name-required":"Nazwa jest wymagana.", + "link":"Link", + "link-required":"Link jest wymagany.", + "columns":"Kolumny" + }, + "quick-links":{ + "title":"Szybkie linki", + "add-link":"Dodaj link", + "add-link-title":"Dodaj szybki link", + "quick-link":"Szybki link", + "quick-link-required":"Szybki link jest wymagany.", + "no-links-matching":"Nie znaleziono linków pasujących do '{{name}}'.", + "columns":"Kolumny" + }, + "recent-dashboards":{ + "title":"Dashboardy", + "last":"Ostatnio przeglądane", + "starred":"Ulubione", + "name":"Nazwa", + "last-viewed":"Ostatnio przeglądane", + "no-last-viewed-dashboards":"Nie oglądałeś jeszcze żadnych dashboardów" + }, + "configured-features":{ + "title":"Skonfigurowane funkcje", + "info":"Status funkcji wymagających konfiguracji", + "email-feature":"Email", + "sms-feature":"SMS", + "slack-feature":"Slack", + "oauth2-feature":"OAuth 2", + "2fa-feature":"2FA", + "feature-configured":"Funkcja jest skonfigurowana.\nKliknij, aby skonfigurować", + "feature-not-configured":"Funkcja nie jest skonfigurowana.\nKliknij, aby skonfigurować" + }, + "version-info":{ + "title":"Wersja", + "contact-us":"Skontaktuj się z nami", + "current-version":"Aktualna wersja", + "current":"Aktualna", + "available-version":"Dostępna wersja", + "available":"Dostępna", + "upgrade":"Aktualizuj", + "version-is-up-to-date":"Wersja jest aktualna" + }, + "usage-info":{ + "title":"Użycie", + "entities":"Encje", + "api-calls":"Wywołania API" + }, + "functions":{ + "title":"Funkcje", + "pe-feature-tooltip":"Tylko w ThingsBoard\nProfessional Edition", + "switch-to-pe":"Przełącz do PE", + "alarms":"Alarmy", + "dashboards":"Dashboards", + "entities-and-relations":"Encje i relacje", + "profiles":"Profile", + "advanced-features":"Zaawansowane funkcje", + "notification-center":"Centrum powiadomień", + "api-usage":"Użycie API", + "customers":"Klienci", + "customers-hierarchy":"Hierarchia klientów", + "roles-and-permissions":"Role i uprawnienia", + "groups":"Grupy", + "integrations":"Integracje", + "solution-templates":"Szablony rozwiązań", + "scheduler":"Harmonogram", + "white-labeling":"White-labeling" + }, + "devices":{ + "view-docs":"Zobacz dokumentację", + "inactive":"Nieaktywne", + "active":"Aktywne", + "total":"Razem" + }, + "alarms":{ + "critical":"Krytyczne", + "assigned-to-me":"Przypisane do mnie", + "total":"Razem" + }, + "getting-started":{ + "get-started":"Rozpocznij", + "finish":"Zakończ", + "done-welcome-title":"Witamy na pokładzie", + "done-welcome-text":"Świetnie sobie poradziłeś!", + "sys-admin":{ + "step1":{ + "title":"Utwórz Najemcę i Administratora Najemcy", + "content":"

    Najemca to osoba lub organizacja, która posiada lub wytwarza urządzenia i zasoby. Najemca może mieć wielu administratorów najemców, klientów, urządzeń i zasobów.

    Administrator najemcy może tworzyć i zarządzać urządzeniami, zasobami, klientami i pulpitami w ramach konta najemcy.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-create-tenant":"Jak utworzyć Najemcę i Administratora Najemcy" + }, + "step2":{ + "title":"Skonfiguruj funkcję: Serwer poczty", + "content":"

    Konfiguracja serwera poczty jest niezbędna do aktywacji użytkowników, odzyskiwania hasła i dostarczania powiadomień o alarmach.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-configure-mail-server":"Jak skonfigurować serwer poczty" + }, + "step3":{ + "title":"Skonfiguruj funkcję: Dostawca SMS", + "content":"

    Skonfiguruj dostawców SMS, aby informować klientów o alarmach za pomocą wiadomości SMS.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-configure-sms-provider":"Jak skonfigurować dostawcę SMS" + }, + "step4":{ + "title":"Skonfiguruj funkcję: 2FA", + "content":"

    Popraw bezpieczeństwo kont platformy za pomocą uwierzytelniania dwuetapowego.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-configure-2fa":"Jak skonfigurować 2FA" + }, + "step5":{ + "title":"Skonfiguruj funkcję: OAuth 2", + "content":"

    Uprość logowanie dla użytkowników najemcy i klientów za pomocą funkcji jednokrotnego logowania za pośrednictwem OAuth 2.0.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-configure-oauth2":"Jak skonfigurować OAuth 2" + }, + "step6":{ + "title":"Skonfiguruj funkcję: Slack", + "content":"

    Rozprowadzaj powiadomienia do użytkowników najemców i klientów za pośrednictwem Slack zgodnie z zasadami powiadomień, które ustawisz.

    Przeczytaj dokumentację, aby się dowiedzieć, jak to zrobić:

    ", + "how-to-configure-notifications":"Jak skonfigurować Slack" + } + }, + "tenant-admin":{ + "step1":{ + "title":"Utwórz urządzenie", + "content":"

    Dodaj swoje pierwsze urządzenie do platformy za pomocą interfejsu użytkownika. Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-create-device":"Jak utworzyć urządzenie" + }, + "step2":{ + "title":"Podłącz urządzenie", + "content-before":"

    Aby podłączyć urządzenie, potrzebujesz danych uwierzytelniających urządzenia. Zalecamy korzystanie z domyślnie generowanych danych uwierzytelniających, czyli token dostępu, zgodnie z tym przewodnikiem.

    • Przejdź do tabeli urządzeń
    • Kliknij w wiersz urządzenia, aby otworzyć szczegóły urządzenia
    • Naciśnij przycisk „Kopiuj token dostępu”

    Użyj prostych poleceń, aby publikować dane za pomocą protokołu HTTP. Nie zapomnij zamienić $ACCESS_TOKEN na swój token dostępu do urządzenia:

    ", + "ubuntu":{ + "install-curl":"Zainstaluj cURL dla Ubuntu:" + }, + "macos":{ + "install-curl":"Zainstaluj cURL dla MacOS:" + }, + "windows":{ + "install-curl":"Od Windows 10 b17063 cURL jest dostępny domyślnie." + }, + "replace-access-token":"Zamień $ACCESS_TOKEN na token dostępu do swojego urządzenia:", + "content-after":"

    Możesz także używać innych protokołów, takich jak MQTT, CoAP, itp.

    Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-connect-device":"Jak podłączyć urządzenie" + }, + "step3":{ + "title":"Utwórz pulpit", + "content":"

    Utwórz pulpit, aby wizualizować dane z jednostek, takich jak zasoby, urządzenia, itp.

    Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-create-dashboard":"Jak utworzyć pulpit" + }, + "step4":{ + "title":"Skonfiguruj reguły alarmowe", + "alarm-rules":"Reguły alarmowe", + "content":"

    Podnieśmy alarm, gdy temperatura osiągnie 25°C. Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-configure-alarm-rules":"Jak skonfigurować reguły alarmowe" + }, + "step5":{ + "title":"Utwórz alarm", + "content-before":"

    Aby uruchomić alarm, prześlij nową wartość telemetrii równej lub wyższej niż 26°C.

    ", + "replace-access-token":"Zamień $ACCESS_TOKEN na token dostępu do swojego urządzenia:", + "content-after":"

    Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-create-alarm":"Jak utworzyć alarm" + }, + "step6":{ + "title":"Utwórz klienta i przypisz pulpit", + "content":"

    Tworząc pulpity dla końcowych użytkowników, użytkownik klienta może widzieć tylko swoje urządzenia, a dane innego klienta będą ukryte.

    Przeczytaj dokumentację, aby dowiedzieć się, jak to zrobić:

    ", + "how-to-create-customer-and-assign-dashboard":"Jak utworzyć klienta i przypisać pulpit" + } + } + } + }, + "color":{ + "color":"Kolor" + }, + "icon":{ + "icon":"Ikona", + "icons":"Ikony", + "select-icon":"Wybierz ikonę", + "material-icons":"Ikony materiałowe", + "show-all":"Pokaż wszystkie ikony", + "search-icon":"Wyszukaj ikonę", + "no-icons-found":"Nie znaleziono ikon dla '{{iconSearch}}'" + }, + "phone-input":{ + "phone-input-label":"Numer telefonu", + "phone-input-required":"Numer telefonu jest wymagany", + "phone-input-validation":"Numer telefonu jest nieprawidłowy lub niemożliwy", + "phone-input-pattern":"Nieprawidłowy numer telefonu. Powinien być w formacie E.164, np. {{phoneNumber}}", + "phone-input-hint":"Numer telefonu w formacie E.164, np. {{phoneNumber}}" + }, + "custom":{ + "widget-action":{ + "action-cell-button":"Przycisk w komórce akcji", + "row-click":"Po kliknięciu w wiersz", + "polygon-click":"Po kliknięciu w wielokąt", + "marker-click":"Po kliknięciu w marker", + "circle-click":"Po kliknięciu w okrąg", + "tooltip-tag-action":"Akcja etykiety narzędziowej", + "node-selected":"Po wybraniu węzła", + "element-click":"Po kliknięciu w element HTML", + "pie-slice-click":"Po kliknięciu w kawałek tarty", + "row-double-click":"Po podwójnym kliknięciu w wiersz", + "card-click":"Po kliknięciu w kartę" + } + }, + "paginator":{ + "items-per-page":"Elementy na stronie:", + "first-page-label":"Pierwsza strona", + "last-page-label":"Ostatnia strona", + "next-page-label":"Następna strona", + "previous-page-label":"Poprzednia strona", + "items-per-page-separator":"z" + }, + "language":{ + "language":"Język", + "locales":{ + "ca_ES":"Kataloński", + "cs_CZ":"Czeski", + "da_DK":"Duński", + "de_DE":"Niemiecki", + "el_GR":"Grecki", + "en_US":"Angielski", + "es_ES":"Hiszpański", + "fa_IR":"Perski", + "fr_FR":"Francuski", + "it_IT":"Włoski", + "ja_JP":"Japoński", + "ka_GE":"Gruziński", + "ko_KR":"Koreański", + "lv_LV":"Łotewski", + "nl_BE":"Niderlandzki (Belgia)", + "pt_BR":"Portugalski (Brazylia)", + "ro_RO":"Rumuński", + "sl_SI":"Słoweński", + "tr_TR":"Turecki", + "uk_UA":"Ukraiński", + "zh_CN":"Chiński uproszczony", + "zh_TW":"Chiński tradycyjny" + } + } +} From 06cba15accc152b0ee412d3ce75af07c2c9d5ae2 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Fri, 9 Feb 2024 14:36:37 +0200 Subject: [PATCH 118/137] UI: Refactoring --- .../assets/locale/locale.constant-pl_PL.json | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json index af6fbb26db..014666017c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json +++ b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json @@ -1704,8 +1704,8 @@ "condition-repeating-value-range":"Liczba zdarzeń powinna mieścić się w zakresie od 1 do 2147483647.", "condition-repeating-value-pattern":"Liczba zdarzeń powinna być liczbą całkowitą.", "condition-repeating-value-required":"Wymagana jest liczba zdarzeń.", - "condition-repeat-times":"Powtarza się {count, plural, =1 {1 raz} other {# razy}}", - "condition-repeat-times-dynamic":"Powtarza się \"{attribute}\" ({count, plural, =1 {1 raz} other {# razy}})", + "condition-repeat-times":"Powtarza się { count, plural, =1 {1 raz} other {# razy} }", + "condition-repeat-times-dynamic":"Powtarza się \"{attribute}\" ({ count, plural, =1 {1 raz} other {# razy} })", "schedule-type":"Typ harmonogramu", "schedule-type-required":"Wymagany jest typ harmonogramu.", "schedule":"Harmonogram", @@ -6698,30 +6698,6 @@ "items-per-page-separator":"z" }, "language":{ - "language":"Język", - "locales":{ - "ca_ES":"Kataloński", - "cs_CZ":"Czeski", - "da_DK":"Duński", - "de_DE":"Niemiecki", - "el_GR":"Grecki", - "en_US":"Angielski", - "es_ES":"Hiszpański", - "fa_IR":"Perski", - "fr_FR":"Francuski", - "it_IT":"Włoski", - "ja_JP":"Japoński", - "ka_GE":"Gruziński", - "ko_KR":"Koreański", - "lv_LV":"Łotewski", - "nl_BE":"Niderlandzki (Belgia)", - "pt_BR":"Portugalski (Brazylia)", - "ro_RO":"Rumuński", - "sl_SI":"Słoweński", - "tr_TR":"Turecki", - "uk_UA":"Ukraiński", - "zh_CN":"Chiński uproszczony", - "zh_TW":"Chiński tradycyjny" - } + "language":"Język" } } From 6943ead99de9e43e19dcc549a77c82e51247c94a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 9 Feb 2024 19:32:58 +0200 Subject: [PATCH 119/137] UI: Power button widget pressed state improvements. --- ui-ngx/package.json | 1 + .../rpc/power-button-widget.component.scss | 4 +- .../lib/rpc/power-button-widget.component.ts | 3 + .../lib/rpc/power-button-widget.models.ts | 213 +++++++++++------- ui-ngx/yarn.lock | 9 +- 5 files changed, 141 insertions(+), 89 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 4bbe817fcf..ab60ebd525 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -44,6 +44,7 @@ "@ngrx/store-devtools": "^15.4.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", + "@svgdotjs/svg.filter.js": "^3.0.8", "@svgdotjs/svg.js": "^3.2.0", "@tinymce/tinymce-angular": "^7.0.0", "ace-builds": "1.4.13", diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss index e0192630f9..d65341a7ba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.scss @@ -46,10 +46,10 @@ justify-content: center; svg { .tb-small-shadow { - filter: drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.08)); + filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.2)); } .tb-shadow { - filter: drop-shadow(8px 8px 8px rgba(0, 0, 0, 0.1)); + filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.15)); } } &.tb-power-button-pointer { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts index e68a2e4baa..02c58b3e9a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts @@ -105,6 +105,7 @@ export class PowerButtonWidgetComponent extends this.loading$.subscribe((loading) => { this.updateDisabledState(loading || this.disabled); + this.cd.markForCheck(); }); } @@ -139,6 +140,7 @@ export class PowerButtonWidgetComponent extends if (this.value !== newValue) { this.value = newValue; this.powerButtonSvgShape?.setValue(this.value); + this.cd.markForCheck(); } } @@ -147,6 +149,7 @@ export class PowerButtonWidgetComponent extends if (this.disabled !== newDisabled) { this.disabled = newDisabled; this.updateDisabledState(this.disabled); + this.cd.markForCheck(); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts index b1372a9660..2e8ca0de6d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.models.ts @@ -24,7 +24,8 @@ import { SetValueSettings, ValueToDataType } from '@shared/models/action-widget-settings.models'; -import { Circle, Element, G, Gradient, Runner, Stop, Svg, Text, Timeline } from '@svgdotjs/svg.js'; +import { Circle, Effect, Element, G, Gradient, Runner, Svg, Text, Timeline } from '@svgdotjs/svg.js'; +import '@svgdotjs/svg.filter.js'; import tinycolor from 'tinycolor2'; import { WidgetContext } from '@home/models/widget-component.models'; @@ -216,6 +217,8 @@ export const powerButtonShapeSize = 110; const cx = powerButtonShapeSize / 2; const cy = powerButtonShapeSize / 2; +const powerButtonAnimation = (element: Element): Runner => element.animate(200, 0, 'now'); + export abstract class PowerButtonShape { static fromSettings(ctx: WidgetContext, @@ -398,20 +401,6 @@ export abstract class PowerButtonShape { shape.maskWith(mask); } - protected createPressedShadow(diameter: number, rightElseLeft = true): Gradient { - const innerShadowGradient = this.svgShape.gradient('radial', (add) => { - add.stop(0.7, '#000000', 0); - add.stop(1, '#000000', 0.4); - }).attr({ cx: rightElseLeft ? '45%' : '55%', cy: '55%', r: '100%'}); - this.svgShape.circle(diameter).center(cx, cy) - .fill(innerShadowGradient).stroke({width: 0}); - return innerShadowGradient; - } - - protected pressedAnimation(element: Element): Runner { - return element.animate(200, 0, 'now'); - } - protected createOnLabel(fontWeight = '500'): Text { return this.createLabel(this.onLabel, fontWeight); } @@ -431,6 +420,72 @@ export abstract class PowerButtonShape { } +class InnerShadowCircle { + + private shadowCircle: Circle; + private blurEffect: Effect; + private offsetEffect: Effect; + private floodEffect: Effect; + + constructor(private svgShape: Svg, + private diameter: number, + private centerX: number, + private centerY: number, + private blur = 6, + private shadowOpacity = 0.6, + private dx = 0, + private dy = 0, + private shadowColor = '#000') { + + this.shadowCircle = this.svgShape.circle(this.diameter).center(this.centerX, this.centerY) + .fill({color: '#fff', opacity: 1}).stroke({width: 0}); + + this.shadowCircle.filterWith(add => { + add.x('-50%').y('-50%').width('200%').height('200%'); + let effect: Effect = add.componentTransfer(components => { + components.funcA({ type: 'table', tableValues: '1 0' }); + }).in(add.$fill); + effect = effect.gaussianBlur(this.blur, this.blur).attr({stdDeviation: this.blur}); + this.blurEffect = effect; + effect = effect.offset(this.dx, this.dy); + this.offsetEffect = effect; + effect = effect.flood(this.shadowColor, this.shadowOpacity); + this.floodEffect = effect; + effect = effect.composite(this.offsetEffect, 'in'); + effect.composite(add.$sourceAlpha, 'in'); + add.merge(m => { + m.mergeNode(add.$fill); + m.mergeNode(); + }); + }); + } + + public timeline(tl: Timeline): void { + this.blurEffect.timeline(tl); + this.offsetEffect.timeline(tl); + this.floodEffect.timeline(tl); + } + + public animate(blur: number, opacity: number, dx = 0, dy = 0): Runner { + powerButtonAnimation(this.blurEffect).attr({stdDeviation: blur}); + powerButtonAnimation(this.offsetEffect).attr({dx, dy}); + return powerButtonAnimation(this.floodEffect).attr({'flood-opacity': opacity}); + } + + public animateRestore(): Runner { + return this.animate(this.blur, this.shadowOpacity, this.dx, this.dy); + } + + public show(): void { + this.shadowCircle.show(); + } + + public hide(): void { + this.shadowCircle.hide(); + } + +} + class DefaultPowerButtonShape extends PowerButtonShape { private outerBorder: Circle; @@ -438,7 +493,7 @@ class DefaultPowerButtonShape extends PowerButtonShape { private offLabelShape: Text; private onCircleShape: Circle; private onLabelShape: Text; - private pressedShadow: Gradient; + private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; @@ -453,7 +508,7 @@ class DefaultPowerButtonShape extends PowerButtonShape { .center(cx, cy); this.onLabelShape = this.createOnLabel(); this.createMask(this.onCircleShape, [this.onLabelShape]); - this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 20); + this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 20, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); @@ -482,16 +537,16 @@ class DefaultPowerButtonShape extends PowerButtonShape { protected onPressStart() { this.pressedTimeline.finish(); const pressedScale = 0.75; - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); - this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onLabelShape).transform({scale: 1}); - this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedShadow.animateRestore(); } } @@ -503,7 +558,7 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { private onCircleShape: Circle; private offLabelShape: Text; private onLabelShape: Text; - private pressedShadow: Gradient; + private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; @@ -517,7 +572,7 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy); this.onLabelShape = this.createOnLabel(); this.createMask(this.onCircleShape, [this.onLabelShape]); - this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 4); + this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); @@ -546,16 +601,16 @@ class SimplifiedPowerButtonShape extends PowerButtonShape { protected onPressStart() { this.pressedTimeline.finish(); const pressedScale = 0.75; - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); - this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onLabelShape).transform({scale: 1}); - this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedShadow.animateRestore(); } } @@ -567,7 +622,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { private offLabelShape: Text; private onCircleShape: Circle; private onLabelShape: Text; - private pressedShadow: Gradient; + private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; private onCenterGroup: G; @@ -588,7 +643,7 @@ class OutlinedPowerButtonShape extends PowerButtonShape { .addTo(this.onCenterGroup); this.onLabelShape = this.createOnLabel(); this.createMask(this.onCircleShape, [this.onLabelShape]); - this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 24); + this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); @@ -617,18 +672,18 @@ class OutlinedPowerButtonShape extends PowerButtonShape { protected onPressStart() { this.pressedTimeline.finish(); const pressedScale = 0.75; - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onCenterGroup).transform({scale: 0.98}); - this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); - this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); - this.pressedAnimation(this.onLabelShape).transform({scale: 1}); - this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedShadow.animateRestore(); } } @@ -639,9 +694,9 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { private innerBorder: Circle; private innerBorderMask: Circle; private innerBorderGradient: Gradient; - private innerShadow: Circle; - private innerShadowGradient: Gradient; - private innerShadowGradientStop: Stop; + private innerShadow: InnerShadowCircle; + //private innerShadowGradient: Gradient; + //private innerShadowGradientStop: Stop; private offLabelShape: Text; private onCircleShape: Circle; private onLabelShape: Text; @@ -670,18 +725,12 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); this.onLabelShape = this.createOnLabel('400'); this.createMask(this.onCircleShape, [this.onLabelShape]); - this.innerShadow = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); - this.innerShadowGradient = this.svgShape.gradient('radial', (add) => { - add.stop(0.7, '#000000', 0); - this.innerShadowGradientStop = add.stop(1, '#000000', 0.2); - }).attr({ cx: '45%', cy: '55%', r: '65%'}); - this.innerShadow.fill(this.innerShadowGradient); + this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 3, 0.3); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); this.onLabelShape.timeline(this.pressedTimeline); - this.innerShadowGradient.timeline(this.pressedTimeline); - this.innerShadowGradientStop.timeline(this.pressedTimeline); + this.innerShadow.timeline(this.pressedTimeline); } protected drawColorState(mainColor: PowerButtonColor){ @@ -724,22 +773,20 @@ class DefaultVolumePowerButtonShape extends PowerButtonShape { this.pressedTimeline.finish(); this.innerShadow.show(); const pressedScale = 0.75; - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale}); - this.pressedAnimation(this.innerShadowGradient).attr({ r: '60%'}); - this.pressedAnimation(this.innerShadowGradientStop).update({ opacity: 0.4 }); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); + this.innerShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onLabelShape).transform({scale: 1}); - this.pressedAnimation(this.innerShadowGradient).attr({ r: '65%'}).after(() => { + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + this.innerShadow.animateRestore().after(() => { if (this.disabled) { this.innerShadow.hide(); } }); - this.pressedAnimation(this.innerShadowGradientStop).update({ opacity: 0.2 }); } } @@ -750,8 +797,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { private outerBorderMask: Circle; private offLabelShape: Text; private onLabelShape: Text; - private innerShadow: Circle; - private pressedShadow: Gradient; + private innerShadow: InnerShadowCircle; + private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; private onCenterGroup: G; @@ -766,14 +813,8 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); this.onCenterGroup = this.svgShape.group(); this.onLabelShape = this.createOnLabel().addTo(this.onCenterGroup); - this.innerShadow = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); - const innerShadowGradient = this.svgShape.gradient('radial', (add) => { - add.stop(0.7, '#000000', 0); - add.stop(1, '#000000', 0.2); - }).attr({ cx: '55%', cy: '55%', r: '65%'}); - this.innerShadow.fill(innerShadowGradient); - this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 4, false); - + this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 3, 0.3); + this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); this.pressedTimeline = new Timeline(); this.centerGroup.timeline(this.pressedTimeline); this.onCenterGroup.timeline(this.pressedTimeline); @@ -807,16 +848,16 @@ class SimplifiedVolumePowerButtonShape extends PowerButtonShape { if (!this.value) { this.backgroundShape.removeClass('tb-shadow'); } - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onCenterGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale}); + this.pressedShadow.animate(8, 0.4); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); - this.pressedAnimation(this.pressedShadow).attr({r: '100%'}).after(() => { + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); + this.pressedShadow.animateRestore().after(() => { if (!this.value) { this.backgroundShape.addClass('tb-shadow'); } @@ -833,7 +874,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { private offLabelShape: Text; private onCircleShape: Circle; private onLabelShape: Text; - private pressedShadow: Gradient; + private pressedShadow: InnerShadowCircle; private pressedTimeline: Timeline; private centerGroup: G; private onCenterGroup: G; @@ -858,7 +899,7 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { .addTo(this.onCenterGroup); this.onLabelShape = this.createOnLabel('800'); this.createMask(this.onCircleShape, [this.onLabelShape]); - this.pressedShadow = this.createPressedShadow(powerButtonShapeSize - 30); + this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 30, cx, cy, 0, 0); this.backgroundShape.addClass('tb-small-shadow'); this.pressedTimeline = new Timeline(); @@ -895,18 +936,18 @@ class OutlinedVolumePowerButtonShape extends PowerButtonShape { protected onPressStart() { this.pressedTimeline.finish(); const pressedScale = 0.75; - this.pressedAnimation(this.centerGroup).transform({scale: pressedScale}); - this.pressedAnimation(this.onCenterGroup).transform({scale: 0.98}); - this.pressedAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); - this.pressedAnimation(this.pressedShadow).attr({r: '62%'}); + powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); + powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); + this.pressedShadow.animate(6, 0.6); } protected onPressEnd() { this.pressedTimeline.finish(); - this.pressedAnimation(this.centerGroup).transform({scale: 1}); - this.pressedAnimation(this.onCenterGroup).transform({scale: 1}); - this.pressedAnimation(this.onLabelShape).transform({scale: 1}); - this.pressedAnimation(this.pressedShadow).attr({r: '100%'}); + powerButtonAnimation(this.centerGroup).transform({scale: 1}); + powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); + powerButtonAnimation(this.onLabelShape).transform({scale: 1}); + this.pressedShadow.animateRestore(); } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 2c60a7f009..d78a9a52d6 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -2747,7 +2747,14 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== -"@svgdotjs/svg.js@^3.2.0": +"@svgdotjs/svg.filter.js@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.8.tgz#998cb2481a871fa70d7dbaa891c886b335c562d7" + integrity sha512-YshF2YDaeRA2StyzAs5nUPrev7npQ38oWD0eTRwnsciSL2KrRPMoUw8BzjIXItb3+dccKGTX3IQOd2NFzmHkog== + dependencies: + "@svgdotjs/svg.js" "^3.1.1" + +"@svgdotjs/svg.js@^3.1.1", "@svgdotjs/svg.js@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@svgdotjs/svg.js/-/svg.js-3.2.0.tgz#6baa8cef6778a93818ac18faa2055222e60aa644" integrity sha512-Tr8p+QVP7y+QT1GBlq1Tt57IvedVH8zCPoYxdHLX0Oof3a/PqnC/tXAkVufv1JQJfsDHlH/UrjcDfgxSofqSNA== From c506d258890fcc170f7c00e6ff4576a52a6b529e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Feb 2024 13:50:04 +0200 Subject: [PATCH 120/137] Update aggregated-value-card-widget.component.ts --- .../widget/lib/cards/aggregated-value-card-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts index dc3b095552..25be87451e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts @@ -241,8 +241,8 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit } else { aggValue.value = 'N/A'; } - const numeric = formatNumberValue(value, (aggValue.key.decimals || this.ctx.decimals)); aggValue.color.update(value); + const numeric = formatNumberValue(value, (aggValue.key.decimals || this.ctx.decimals)); if (aggValue.showArrow && isDefined(numeric)) { aggValue.upArrow = numeric > 0; aggValue.downArrow = numeric < 0; From a4c5617cc2a849bb74a1337172f65ec3fe1a6e95 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Mon, 12 Feb 2024 16:24:02 +0200 Subject: [PATCH 121/137] Improve edge service to find active state based on persistToTelemetry else attribute --- .../device/DeviceActorMessageProcessor.java | 3 +-- .../rpc/processor/BaseEdgeProcessorTest.java | 4 +++ .../server/dao/edge/EdgeService.java | 2 ++ .../server/dao/edge/EdgeServiceImpl.java | 26 ++++++++++++++++--- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 9f92838712..f1c0260124 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -213,8 +213,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso if (systemContext.isEdgesEnabled() && edgeId != null) { log.debug("[{}][{}] device is related to edge: [{}]. Saving RPC request: [{}][{}] to edge queue", tenantId, deviceId, edgeId.getId(), rpcId, requestId); try { - Optional edgeAttributeOpt = systemContext.getAttributesService().find(tenantId, edgeId, DataConstants.SERVER_SCOPE, DefaultDeviceStateService.ACTIVITY_STATE).get(); - if (edgeAttributeOpt.isPresent() && edgeAttributeOpt.get().getBooleanValue().orElse(false)) { + if (systemContext.getEdgeService().isEdgeActiveAsync(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE).get()) { saveRpcRequestToEdgeQueue(request, requestId).get(); } else { log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}. The Edge is currently offline or unreachable", tenantId, deviceId, edgeId.getId(), request); diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java index 9c45977a7c..835b39caf9 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -55,6 +55,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -209,6 +210,9 @@ public abstract class BaseEdgeProcessorTest { @MockBean protected AttributesService attributesService; + @MockBean + protected TimeseriesService timeseriesService; + @MockBean protected TbClusterService tbClusterService; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index 15e55d8713..d6d5551ccf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -91,4 +91,6 @@ public interface EdgeService extends EntityDaoService { PageData findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName); + + ListenableFuture isEdgeActiveAsync(TenantId tenantId, EdgeId edgeId, String activityState); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 80eea0a478..136fd0485d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -30,10 +30,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; @@ -48,14 +48,15 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; @@ -64,7 +65,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; @@ -105,7 +106,10 @@ public class EdgeServiceImpl extends AbstractCachedEntityService edgeValidator; @@ -113,6 +117,8 @@ public class EdgeServiceImpl extends AbstractCachedEntityService isEdgeActiveAsync(TenantId tenantId, EdgeId edgeId, String key) { + ListenableFuture> futureKvEntry; + if (persistToTelemetry) { + futureKvEntry = timeseriesService.findLatest(tenantId, edgeId, key); + } else { + futureKvEntry = attributesService.find(tenantId, edgeId, DataConstants.SERVER_SCOPE, key); + } + return Futures.transformAsync(futureKvEntry, kvEntryOpt -> + Futures.immediateFuture(kvEntryOpt.flatMap(KvEntry::getBooleanValue).orElse(false)), MoreExecutors.directExecutor()); + } + private List findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { List result = new ArrayList<>(); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); From e537479b884191dc67b6ff98ba636708a60406db Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Feb 2024 13:53:44 +0200 Subject: [PATCH 122/137] UI: Implement slider widget. --- .../widget_bundles/control_widgets.json | 1 + .../data/json/system/widget_types/slider.json | 39 ++ .../basic/basic-widget-config.module.ts | 10 +- .../power-button-basic-config.component.html | 6 +- .../single-switch-basic-config.component.html | 4 +- .../rpc/slider-basic-config.component.html | 270 ++++++++++++++ .../rpc/slider-basic-config.component.ts | 309 ++++++++++++++++ .../widget/lib/action/action-widget.models.ts | 11 +- .../widget/lib/action/action-widget.scss | 31 -- .../action-button-widget.component.html | 6 - .../command-button-widget.component.html | 6 - .../rpc/power-button-widget.component.html | 6 - .../rpc/single-switch-widget.component.html | 6 - .../lib/rpc/slider-widget.component.html | 59 +++ .../lib/rpc/slider-widget.component.scss | 135 +++++++ .../widget/lib/rpc/slider-widget.component.ts | 347 ++++++++++++++++++ .../widget/lib/rpc/slider-widget.models.ts | 196 ++++++++++ ...ower-button-widget-settings.component.html | 6 +- .../widget-button-appearance.component.html | 2 +- ...ngle-switch-widget-settings.component.html | 4 +- .../slider-widget-settings.component.html | 215 +++++++++++ .../slider-widget-settings.component.ts | 186 ++++++++++ .../lib/settings/widget-settings.module.ts | 12 +- .../widget/widget-components.module.ts | 7 +- .../app/shared/components/toast.directive.ts | 1 + .../assets/locale/locale.constant-en_US.json | 25 ++ .../assets/widget/slider/default-layout.svg | 48 +++ .../assets/widget/slider/extended-layout.svg | 55 +++ .../widget/slider/simplified-layout.svg | 47 +++ 29 files changed, 1969 insertions(+), 81 deletions(-) create mode 100644 application/src/main/data/json/system/widget_types/slider.json create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts create mode 100644 ui-ngx/src/assets/widget/slider/default-layout.svg create mode 100644 ui-ngx/src/assets/widget/slider/extended-layout.svg create mode 100644 ui-ngx/src/assets/widget/slider/simplified-layout.svg diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 568a144874..560063a2c4 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -11,6 +11,7 @@ "single_switch", "command_button", "power_button", + "slider", "control_widgets.switch_control", "control_widgets.slide_toggle_control", "control_widgets.round_switch", diff --git a/application/src/main/data/json/system/widget_types/slider.json b/application/src/main/data/json/system/widget_types/slider.json new file mode 100644 index 0000000000..8cbf5326c3 --- /dev/null +++ b/application/src/main/data/json/system/widget_types/slider.json @@ -0,0 +1,39 @@ +{ + "fqn": "slider", + "name": "Slider", + "deprecated": false, + "image": "tb-image:SG9yaXpvbnRhbCBzbGlkZXIuc3Zn:IlNsaWRlciIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik05MC41MDY0IDYwLjkxOFY2My4xNjhINzguMjAxN0w3OC4xMDggNjEuNDY4OEw4NS40NjczIDQ5LjkzNzVIODcuNzI5MUw4NS4yNzk4IDU0LjEzMjhMODEuMDQ5NCA2MC45MThIOTAuNTA2NFpNODguMzczNiA0OS45Mzc1VjY3SDg1LjU0OTRWNDkuOTM3NUg4OC4zNzM2Wk0xMDMuNjA5IDYyLjM0NzdDMTAzLjYwOSA2My40MTAyIDEwMy4zNjMgNjQuMzA0NyAxMDIuODcxIDY1LjAzMTJDMTAyLjM3OSA2NS43NTc4IDEwMS43MDcgNjYuMzA4NiAxMDAuODU1IDY2LjY4MzZDMTAwLjAxMiA2Ny4wNTA4IDk5LjA1ODYgNjcuMjM0NCA5Ny45OTYxIDY3LjIzNDRDOTYuOTMzNiA2Ny4yMzQ0IDk1Ljk3NjYgNjcuMDUwOCA5NS4xMjUgNjYuNjgzNkM5NC4yNzM0IDY2LjMwODYgOTMuNjAxNiA2NS43NTc4IDkzLjEwOTQgNjUuMDMxMkM5Mi42MTcyIDY0LjMwNDcgOTIuMzcxMSA2My40MTAyIDkyLjM3MTEgNjIuMzQ3N0M5Mi4zNzExIDYxLjY0NDUgOTIuNTA3OCA2MS4wMDc4IDkyLjc4MTMgNjAuNDM3NUM5My4wNTQ3IDU5Ljg1OTQgOTMuNDQxNCA1OS4zNjMzIDkzLjk0MTQgNTguOTQ5MkM5NC40NDkyIDU4LjUyNzMgOTUuMDQzIDU4LjIwMzEgOTUuNzIyNyA1Ny45NzY2Qzk2LjQxMDIgNTcuNzUgOTcuMTYwMiA1Ny42MzY3IDk3Ljk3MjcgNTcuNjM2N0M5OS4wNTA4IDU3LjYzNjcgMTAwLjAxNiA1Ny44MzU5IDEwMC44NjcgNTguMjM0NEMxMDEuNzE5IDU4LjYzMjggMTAyLjM4NyA1OS4xODM2IDEwMi44NzEgNTkuODg2N0MxMDMuMzYzIDYwLjU4OTggMTAzLjYwOSA2MS40MTAyIDEwMy42MDkgNjIuMzQ3N1pNMTAwLjc3MyA2Mi4yMDdDMTAwLjc3MyA2MS42MzY3IDEwMC42NTYgNjEuMTM2NyAxMDAuNDIyIDYwLjcwN0MxMDAuMTg4IDYwLjI3NzMgOTkuODU5NCA1OS45NDUzIDk5LjQzNzUgNTkuNzEwOUM5OS4wMTU2IDU5LjQ3NjYgOTguNTI3MyA1OS4zNTk0IDk3Ljk3MjcgNTkuMzU5NEM5Ny40MTAyIDU5LjM1OTQgOTYuOTIxOSA1OS40NzY2IDk2LjUwNzggNTkuNzEwOUM5Ni4wOTM4IDU5Ljk0NTMgOTUuNzY5NSA2MC4yNzczIDk1LjUzNTIgNjAuNzA3Qzk1LjMwODYgNjEuMTM2NyA5NS4xOTUzIDYxLjYzNjcgOTUuMTk1MyA2Mi4yMDdDOTUuMTk1MyA2Mi43ODUyIDk1LjMwODYgNjMuMjg1MiA5NS41MzUyIDYzLjcwN0M5NS43NjE3IDY0LjEyMTEgOTYuMDg1OSA2NC40Mzc1IDk2LjUwNzggNjQuNjU2MkM5Ni45Mjk3IDY0Ljg3NSA5Ny40MjU4IDY0Ljk4NDQgOTcuOTk2MSA2NC45ODQ0Qzk4LjU2NjQgNjQuOTg0NCA5OS4wNTg2IDY0Ljg3NSA5OS40NzI3IDY0LjY1NjJDOTkuODg2NyA2NC40Mzc1IDEwMC4yMDcgNjQuMTIxMSAxMDAuNDM0IDYzLjcwN0MxMDAuNjYgNjMuMjg1MiAxMDAuNzczIDYyLjc4NTIgMTAwLjc3MyA2Mi4yMDdaTTEwMy4yMjMgNTQuNDI1OEMxMDMuMjIzIDU1LjI3NzMgMTAyLjk5NiA1Ni4wMzUyIDEwMi41NDMgNTYuNjk5MkMxMDIuMDk4IDU3LjM2MzMgMTAxLjQ4IDU3Ljg4NjcgMTAwLjY5MSA1OC4yNjk1Qzk5LjkwMjMgNTguNjQ0NSA5OS4wMDM5IDU4LjgzMiA5Ny45OTYxIDU4LjgzMkM5Ni45ODA1IDU4LjgzMiA5Ni4wNzQyIDU4LjY0NDUgOTUuMjc3MyA1OC4yNjk1Qzk0LjQ4ODMgNTcuODg2NyA5My44NjcyIDU3LjM2MzMgOTMuNDE0MSA1Ni42OTkyQzkyLjk2ODggNTYuMDM1MiA5Mi43NDYxIDU1LjI3NzMgOTIuNzQ2MSA1NC40MjU4QzkyLjc0NjEgNTMuNDEwMiA5Mi45Njg4IDUyLjU1NDcgOTMuNDE0MSA1MS44NTk0QzkzLjg2NzIgNTEuMTU2MiA5NC40ODgzIDUwLjYyMTEgOTUuMjc3MyA1MC4yNTM5Qzk2LjA2NjQgNDkuODg2NyA5Ni45Njg4IDQ5LjcwMzEgOTcuOTg0NCA0OS43MDMxQzk5IDQ5LjcwMzEgOTkuOTAyMyA0OS44ODY3IDEwMC42OTEgNTAuMjUzOUMxMDEuNDggNTAuNjIxMSAxMDIuMDk4IDUxLjE1NjIgMTAyLjU0MyA1MS44NTk0QzEwMi45OTYgNTIuNTU0NyAxMDMuMjIzIDUzLjQxMDIgMTAzLjIyMyA1NC40MjU4Wk0xMDAuMzk4IDU0LjUxOTVDMTAwLjM5OCA1NC4wMTE3IDEwMC4yOTcgNTMuNTY2NCAxMDAuMDk0IDUzLjE4MzZDOTkuODk4NCA1Mi43OTMgOTkuNjIxMSA1Mi40ODgzIDk5LjI2MTcgNTIuMjY5NUM5OC45MDIzIDUyLjA1MDggOTguNDc2NiA1MS45NDE0IDk3Ljk4NDQgNTEuOTQxNEM5Ny40OTIyIDUxLjk0MTQgOTcuMDY2NCA1Mi4wNDY5IDk2LjcwNyA1Mi4yNTc4Qzk2LjM0NzcgNTIuNDY4OCA5Ni4wNzAzIDUyLjc2NTYgOTUuODc1IDUzLjE0ODRDOTUuNjc5NyA1My41MzEyIDk1LjU4MiA1My45ODgzIDk1LjU4MiA1NC41MTk1Qzk1LjU4MiA1NS4wNDMgOTUuNjc5NyA1NS41IDk1Ljg3NSA1NS44OTA2Qzk2LjA3MDMgNTYuMjczNCA5Ni4zNDc3IDU2LjU3NDIgOTYuNzA3IDU2Ljc5M0M5Ny4wNzQyIDU3LjAxMTcgOTcuNTAzOSA1Ny4xMjExIDk3Ljk5NjEgNTcuMTIxMUM5OC40ODgzIDU3LjEyMTEgOTguOTE0MSA1Ny4wMTE3IDk5LjI3MzQgNTYuNzkzQzk5LjYzMjggNTYuNTc0MiA5OS45MTAyIDU2LjI3MzQgMTAwLjEwNSA1NS44OTA2QzEwMC4zMDEgNTUuNSAxMDAuMzk4IDU1LjA0MyAxMDAuMzk4IDU0LjUxOTVaTTEwNi4wMzcgNTQuMTIxMVY1My4yMTg4QzEwNi4wMzcgNTIuNTcwMyAxMDYuMTc3IDUxLjk4MDUgMTA2LjQ1OCA1MS40NDkyQzEwNi43NCA1MC45MTggMTA3LjE1IDUwLjQ5MjIgMTA3LjY4OSA1MC4xNzE5QzEwOC4yMjggNDkuODUxNiAxMDguODc2IDQ5LjY5MTQgMTA5LjYzNCA0OS42OTE0QzExMC40MTUgNDkuNjkxNCAxMTEuMDcyIDQ5Ljg1MTYgMTExLjYwMyA1MC4xNzE5QzExMi4xNDIgNTAuNDkyMiAxMTIuNTUyIDUwLjkxOCAxMTIuODMzIDUxLjQ0OTJDMTEzLjExNSA1MS45ODA1IDExMy4yNTUgNTIuNTcwMyAxMTMuMjU1IDUzLjIxODhWNTQuMTIxMUMxMTMuMjU1IDU0Ljc1MzkgMTEzLjExNSA1NS4zMzU5IDExMi44MzMgNTUuODY3MkMxMTIuNTYgNTYuMzk4NCAxMTIuMTU0IDU2LjgyNDIgMTExLjYxNSA1Ny4xNDQ1QzExMS4wODMgNTcuNDY0OCAxMTAuNDMxIDU3LjYyNSAxMDkuNjU4IDU3LjYyNUMxMDguODkyIDU3LjYyNSAxMDguMjM2IDU3LjQ2NDggMTA3LjY4OSA1Ny4xNDQ1QzEwNy4xNSA1Ni44MjQyIDEwNi43NCA1Ni4zOTg0IDEwNi40NTggNTUuODY3MkMxMDYuMTc3IDU1LjMzNTkgMTA2LjAzNyA1NC43NTM5IDEwNi4wMzcgNTQuMTIxMVpNMTA3Ljk5NCA1My4yMTg4VjU0LjEyMTFDMTA3Ljk5NCA1NC40MzM2IDEwOC4wNTIgNTQuNzMwNSAxMDguMTY5IDU1LjAxMTdDMTA4LjI5NCA1NS4yOTMgMTA4LjQ4MiA1NS41MTk1IDEwOC43MzIgNTUuNjkxNEMxMDguOTgyIDU1Ljg2MzMgMTA5LjI5IDU1Ljk0OTIgMTA5LjY1OCA1NS45NDkyQzExMC4wMzMgNTUuOTQ5MiAxMTAuMzM3IDU1Ljg2MzMgMTEwLjU3MiA1NS42OTE0QzExMC44MTQgNTUuNTE5NSAxMTAuOTk0IDU1LjI5MyAxMTEuMTExIDU1LjAxMTdDMTExLjIyOCA1NC43MzA1IDExMS4yODcgNTQuNDMzNiAxMTEuMjg3IDU0LjEyMTFWNTMuMjE4OEMxMTEuMjg3IDUyLjg5ODQgMTExLjIyNCA1Mi41OTc3IDExMS4wOTkgNTIuMzE2NEMxMTAuOTgyIDUyLjAyNzMgMTEwLjgwMiA1MS43OTY5IDExMC41NiA1MS42MjVDMTEwLjMxOCA1MS40NTMxIDExMC4wMDkgNTEuMzY3MiAxMDkuNjM0IDUxLjM2NzJDMTA5LjI3NSA1MS4zNjcyIDEwOC45NyA1MS40NTMxIDEwOC43MiA1MS42MjVDMTA4LjQ3OCA1MS43OTY5IDEwOC4yOTQgNTIuMDI3MyAxMDguMTY5IDUyLjMxNjRDMTA4LjA1MiA1Mi41OTc3IDEwNy45OTQgNTIuODk4NCAxMDcuOTk0IDUzLjIxODhaTTExNC4yNjMgNjMuNzMwNVY2Mi44MTY0QzExNC4yNjMgNjIuMTc1OCAxMTQuNDA0IDYxLjU4OTggMTE0LjY4NSA2MS4wNTg2QzExNC45NzQgNjAuNTI3MyAxMTUuMzg4IDYwLjEwMTYgMTE1LjkyNyA1OS43ODEyQzExNi40NjYgNTkuNDYwOSAxMTcuMTE1IDU5LjMwMDggMTE3Ljg3MyA1OS4zMDA4QzExOC42NTQgNTkuMzAwOCAxMTkuMzEgNTkuNDYwOSAxMTkuODQxIDU5Ljc4MTJDMTIwLjM4IDYwLjEwMTYgMTIwLjc4NyA2MC41MjczIDEyMS4wNiA2MS4wNTg2QzEyMS4zNDEgNjEuNTg5OCAxMjEuNDgyIDYyLjE3NTggMTIxLjQ4MiA2Mi44MTY0VjYzLjczMDVDMTIxLjQ4MiA2NC4zNzExIDEyMS4zNDEgNjQuOTU3IDEyMS4wNiA2NS40ODgzQzEyMC43ODcgNjYuMDE5NSAxMjAuMzg0IDY2LjQ0NTMgMTE5Ljg1MyA2Ni43NjU2QzExOS4zMjIgNjcuMDg1OSAxMTguNjczIDY3LjI0NjEgMTE3LjkwOCA2Ny4yNDYxQzExNy4xMzQgNjcuMjQ2MSAxMTYuNDc0IDY3LjA4NTkgMTE1LjkyNyA2Ni43NjU2QzExNS4zODggNjYuNDQ1MyAxMTQuOTc0IDY2LjAxOTUgMTE0LjY4NSA2NS40ODgzQzExNC40MDQgNjQuOTU3IDExNC4yNjMgNjQuMzcxMSAxMTQuMjYzIDYzLjczMDVaTTExNi4yMzIgNjIuODE2NFY2My43MzA1QzExNi4yMzIgNjQuMDQzIDExNi4yOTQgNjQuMzM5OCAxMTYuNDE5IDY0LjYyMTFDMTE2LjU1MiA2NC45MDIzIDExNi43NDQgNjUuMTMyOCAxMTYuOTk0IDY1LjMxMjVDMTE3LjI0NCA2NS40ODQ0IDExNy41NDQgNjUuNTcwMyAxMTcuODk2IDY1LjU3MDNDMTE4LjI5NCA2NS41NzAzIDExOC42MTUgNjUuNDg0NCAxMTguODU3IDY1LjMxMjVDMTE5LjA5OSA2NS4xMzI4IDExOS4yNzEgNjQuOTA2MiAxMTkuMzczIDY0LjYzMjhDMTE5LjQ4MiA2NC4zNTE2IDExOS41MzcgNjQuMDUwOCAxMTkuNTM3IDYzLjczMDVWNjIuODE2NEMxMTkuNTM3IDYyLjQ5NjEgMTE5LjQ3NCA2Mi4xOTUzIDExOS4zNDkgNjEuOTE0MUMxMTkuMjMyIDYxLjYzMjggMTE5LjA0OCA2MS40MDYyIDExOC43OTggNjEuMjM0NEMxMTguNTU2IDYxLjA2MjUgMTE4LjI0OCA2MC45NzY2IDExNy44NzMgNjAuOTc2NkMxMTcuNTA1IDYwLjk3NjYgMTE3LjIwMSA2MS4wNjI1IDExNi45NTggNjEuMjM0NEMxMTYuNzE2IDYxLjQwNjIgMTE2LjUzMyA2MS42MzI4IDExNi40MDggNjEuOTE0MUMxMTYuMjkgNjIuMTk1MyAxMTYuMjMyIDYyLjQ5NjEgMTE2LjIzMiA2Mi44MTY0Wk0xMTguNTc2IDUyLjM3NUwxMTAuMjQ0IDY1LjcxMDlMMTA4LjgwMiA2NC44Nzg5TDExNy4xMzQgNTEuNTQzTDExOC41NzYgNTIuMzc1WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cmVjdCB5PSI3OSIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI4IiByeD0iMi43MDE3NCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjMiLz4KPGNpcmNsZSBjeD0iMi45MTk0NiIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE4LjI1MjUiIGN5PSI4MyIgcj0iMC41Njg4NzgiIGZpbGw9IiM1NDY5RkYiIGZpbGwtb3BhY2l0eT0iMC44MiIvPgo8Y2lyY2xlIGN4PSIzMy41ODQ1IiBjeT0iODMiIHI9IjAuNTY4ODc4IiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iNDguOTE2NSIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjY0LjI0ODYiIGN5PSI4MyIgcj0iMC41Njg4NzgiIGZpbGw9IiM1NDY5RkYiIGZpbGwtb3BhY2l0eT0iMC44MiIvPgo8Y2lyY2xlIGN4PSI3OS41ODA2IiBjeT0iODMiIHI9IjAuNTY4ODc4IiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iOTQuOTEzNiIgY3k9IjgzIiByPSIwLjU2ODg3OCIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjExMC42NzciIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjEyNi44NzEiIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE0My4wNjUiIGN5PSI4MyIgcj0iMSIgZmlsbD0iIzU0NjlGRiIgZmlsbC1vcGFjaXR5PSIwLjgyIi8+CjxjaXJjbGUgY3g9IjE1OS4yNiIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iMTc1LjQ1NSIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPGNpcmNsZSBjeD0iMTkxLjY0OSIgY3k9IjgzIiByPSIxIiBmaWxsPSIjNTQ2OUZGIiBmaWxsLW9wYWNpdHk9IjAuODIiLz4KPHJlY3QgeT0iNzkiIHdpZHRoPSIxMDEiIGhlaWdodD0iOCIgcng9IjIuNzAxNzQiIGZpbGw9IiM1NDY5RkYiLz4KPHBhdGggZD0iTTUuNTU5MDggOTUuNDY2OFY5Ni42NTkyQzUuNTU5MDggOTcuMzAwMSA1LjUwMTc5IDk3Ljg0MDggNS4zODcyMSA5OC4yODEyQzUuMjcyNjIgOTguNzIxNyA1LjEwNzkxIDk5LjA3NjIgNC44OTMwNyA5OS4zNDQ3QzQuNjc4MjIgOTkuNjEzMyA0LjQxODYyIDk5LjgwODQgNC4xMTQyNiA5OS45MzAyQzMuODEzNDggMTAwLjA0OCAzLjQ3MzMxIDEwMC4xMDcgMy4wOTM3NSAxMDAuMTA3QzIuNzkyOTcgMTAwLjEwNyAyLjUxNTQ2IDEwMC4wNyAyLjI2MTIzIDk5Ljk5NDZDMi4wMDcgOTkuOTE5NCAxLjc3NzgzIDk5Ljc5OTUgMS41NzM3MyA5OS42MzQ4QzEuMzczMjEgOTkuNDY2NSAxLjIwMTMzIDk5LjI0OCAxLjA1ODExIDk4Ljk3OTVDMC45MTQ4NzYgOTguNzEwOSAwLjgwNTY2NCA5OC4zODUxIDAuNzMwNDY5IDk4LjAwMkMwLjY1NTI3MyA5Ny42MTg4IDAuNjE3Njc2IDk3LjE3MTIgMC42MTc2NzYgOTYuNjU5MlY5NS40NjY4QzAuNjE3Njc2IDk0LjgyNTggMC42NzQ5NjcgOTQuMjg4NyAwLjc4OTU1MSA5My44NTU1QzAuOTA3NzE1IDkzLjQyMjIgMS4wNzQyMiA5My4wNzQ5IDEuMjg5MDYgOTIuODEzNUMxLjUwMzkxIDkyLjU0ODUgMS43NjE3MiA5Mi4zNTg3IDIuMDYyNSA5Mi4yNDQxQzIuMzY2ODYgOTIuMTI5NiAyLjcwNzAzIDkyLjA3MjMgMy4wODMwMSA5Mi4wNzIzQzMuMzg3MzcgOTIuMDcyMyAzLjY2NjY3IDkyLjEwOTkgMy45MjA5IDkyLjE4NTFDNC4xNzg3MSA5Mi4yNTY3IDQuNDA3ODggOTIuMzczIDQuNjA4NCA5Mi41MzQyQzQuODA4OTIgOTIuNjkxNyA0Ljk3OSA5Mi45MDMgNS4xMTg2NSA5My4xNjhDNS4yNjE4OCA5My40Mjk0IDUuMzcxMDkgOTMuNzQ5OCA1LjQ0NjI5IDk0LjEyOTRDNS41MjE0OCA5NC41MDkgNS41NTkwOCA5NC45NTQ4IDUuNTU5MDggOTUuNDY2OFpNNC41NjAwNiA5Ni44MjAzVjk1LjMwMDNDNC41NjAwNiA5NC45NDk0IDQuNTM4NTcgOTQuNjQxNCA0LjQ5NTYxIDk0LjM3NjVDNC40NTYyMiA5NC4xMDc5IDQuMzk3MTQgOTMuODc4NyA0LjMxODM2IDkzLjY4OUM0LjIzOTU4IDkzLjQ5OTIgNC4xMzkzMiA5My4zNDUyIDQuMDE3NTggOTMuMjI3MUMzLjg5OTQxIDkzLjEwODkgMy43NjE1NiA5My4wMjI5IDMuNjA0IDkyLjk2OTJDMy40NTAwMyA5Mi45MTE5IDMuMjc2MzcgOTIuODgzMyAzLjA4MzAxIDkyLjg4MzNDMi44NDY2OCA5Mi44ODMzIDIuNjM3MjEgOTIuOTI4MSAyLjQ1NDU5IDkzLjAxNzZDMi4yNzE5NyA5My4xMDM1IDIuMTE4IDkzLjI0MTQgMS45OTI2OCA5My40MzEyQzEuODcwOTMgOTMuNjIwOSAxLjc3NzgzIDkzLjg2OTggMS43MTMzOCA5NC4xNzc3QzEuNjQ4OTMgOTQuNDg1NyAxLjYxNjcgOTQuODU5OSAxLjYxNjcgOTUuMzAwM1Y5Ni44MjAzQzEuNjE2NyA5Ny4xNzEyIDEuNjM2MzkgOTcuNDgxIDEuNjc1NzggOTcuNzQ5NUMxLjcxODc1IDk4LjAxODEgMS43ODE0MSA5OC4yNTA4IDEuODYzNzcgOTguNDQ3OEMxLjk0NjEzIDk4LjY0MTEgMi4wNDYzOSA5OC44MDA1IDIuMTY0NTUgOTguOTI1OEMyLjI4MjcxIDk5LjA1MTEgMi40MTg3OCA5OS4xNDQyIDIuNTcyNzUgOTkuMjA1MUMyLjczMDMxIDk5LjI2MjQgMi45MDM5NyA5OS4yOTEgMy4wOTM3NSA5OS4yOTFDMy4zMzcyNCA5OS4yOTEgMy41NTAyOSA5OS4yNDQ1IDMuNzMyOTEgOTkuMTUxNEMzLjkxNTUzIDk5LjA1ODMgNC4wNjc3MSA5OC45MTMyIDQuMTg5NDUgOTguNzE2M0M0LjMxNDc4IDk4LjUxNTggNC40MDc4OCA5OC4yNTk4IDQuNDY4NzUgOTcuOTQ4MkM0LjUyOTYyIDk3LjYzMzEgNC41NjAwNiA5Ny4yNTcyIDQuNTYwMDYgOTYuODIwM1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTE4NC44NTMgOTIuMTM2N1YxMDBIMTgzLjg1OVY5My4zNzc0TDE4MS44NTYgOTQuMTA3OVY5My4yMTA5TDE4NC42OTcgOTIuMTM2N0gxODQuODUzWk0xOTIuOTM0IDk1LjQ2NjhWOTYuNjU5MkMxOTIuOTM0IDk3LjMwMDEgMTkyLjg3NyA5Ny44NDA4IDE5Mi43NjIgOTguMjgxMkMxOTIuNjQ4IDk4LjcyMTcgMTkyLjQ4MyA5OS4wNzYyIDE5Mi4yNjggOTkuMzQ0N0MxOTIuMDUzIDk5LjYxMzMgMTkxLjc5NCA5OS44MDg0IDE5MS40ODkgOTkuOTMwMkMxOTEuMTg4IDEwMC4wNDggMTkwLjg0OCAxMDAuMTA3IDE5MC40NjkgMTAwLjEwN0MxOTAuMTY4IDEwMC4xMDcgMTg5Ljg5IDEwMC4wNyAxODkuNjM2IDk5Ljk5NDZDMTg5LjM4MiA5OS45MTk0IDE4OS4xNTMgOTkuNzk5NSAxODguOTQ5IDk5LjYzNDhDMTg4Ljc0OCA5OS40NjY1IDE4OC41NzYgOTkuMjQ4IDE4OC40MzMgOTguOTc5NUMxODguMjkgOTguNzEwOSAxODguMTgxIDk4LjM4NTEgMTg4LjEwNSA5OC4wMDJDMTg4LjAzIDk3LjYxODggMTg3Ljk5MyA5Ny4xNzEyIDE4Ny45OTMgOTYuNjU5MlY5NS40NjY4QzE4Ny45OTMgOTQuODI1OCAxODguMDUgOTQuMjg4NyAxODguMTY1IDkzLjg1NTVDMTg4LjI4MyA5My40MjIyIDE4OC40NDkgOTMuMDc0OSAxODguNjY0IDkyLjgxMzVDMTg4Ljg3OSA5Mi41NDg1IDE4OS4xMzcgOTIuMzU4NyAxODkuNDM4IDkyLjI0NDFDMTg5Ljc0MiA5Mi4xMjk2IDE5MC4wODIgOTIuMDcyMyAxOTAuNDU4IDkyLjA3MjNDMTkwLjc2MiA5Mi4wNzIzIDE5MS4wNDIgOTIuMTA5OSAxOTEuMjk2IDkyLjE4NTFDMTkxLjU1NCA5Mi4yNTY3IDE5MS43ODMgOTIuMzczIDE5MS45ODMgOTIuNTM0MkMxOTIuMTg0IDkyLjY5MTcgMTkyLjM1NCA5Mi45MDMgMTkyLjQ5NCA5My4xNjhDMTkyLjYzNyA5My40Mjk0IDE5Mi43NDYgOTMuNzQ5OCAxOTIuODIxIDk0LjEyOTRDMTkyLjg5NiA5NC41MDkgMTkyLjkzNCA5NC45NTQ4IDE5Mi45MzQgOTUuNDY2OFpNMTkxLjkzNSA5Ni44MjAzVjk1LjMwMDNDMTkxLjkzNSA5NC45NDk0IDE5MS45MTQgOTQuNjQxNCAxOTEuODcxIDk0LjM3NjVDMTkxLjgzMSA5NC4xMDc5IDE5MS43NzIgOTMuODc4NyAxOTEuNjkzIDkzLjY4OUMxOTEuNjE1IDkzLjQ5OTIgMTkxLjUxNCA5My4zNDUyIDE5MS4zOTMgOTMuMjI3MUMxOTEuMjc0IDkzLjEwODkgMTkxLjEzNyA5My4wMjI5IDE5MC45NzkgOTIuOTY5MkMxOTAuODI1IDkyLjkxMTkgMTkwLjY1MSA5Mi44ODMzIDE5MC40NTggOTIuODgzM0MxOTAuMjIyIDkyLjg4MzMgMTkwLjAxMiA5Mi45MjgxIDE4OS44MyA5My4wMTc2QzE4OS42NDcgOTMuMTAzNSAxODkuNDkzIDkzLjI0MTQgMTg5LjM2OCA5My40MzEyQzE4OS4yNDYgOTMuNjIwOSAxODkuMTUzIDkzLjg2OTggMTg5LjA4OCA5NC4xNzc3QzE4OS4wMjQgOTQuNDg1NyAxODguOTkyIDk0Ljg1OTkgMTg4Ljk5MiA5NS4zMDAzVjk2LjgyMDNDMTg4Ljk5MiA5Ny4xNzEyIDE4OS4wMTEgOTcuNDgxIDE4OS4wNTEgOTcuNzQ5NUMxODkuMDk0IDk4LjAxODEgMTg5LjE1NiA5OC4yNTA4IDE4OS4yMzkgOTguNDQ3OEMxODkuMzIxIDk4LjY0MTEgMTg5LjQyMSA5OC44MDA1IDE4OS41NCA5OC45MjU4QzE4OS42NTggOTkuMDUxMSAxODkuNzk0IDk5LjE0NDIgMTg5Ljk0OCA5OS4yMDUxQzE5MC4xMDUgOTkuMjYyNCAxOTAuMjc5IDk5LjI5MSAxOTAuNDY5IDk5LjI5MUMxOTAuNzEyIDk5LjI5MSAxOTAuOTI1IDk5LjI0NDUgMTkxLjEwOCA5OS4xNTE0QzE5MS4yOTEgOTkuMDU4MyAxOTEuNDQzIDk4LjkxMzIgMTkxLjU2NCA5OC43MTYzQzE5MS42OSA5OC41MTU4IDE5MS43ODMgOTguMjU5OCAxOTEuODQ0IDk3Ljk0ODJDMTkxLjkwNSA5Ny42MzMxIDE5MS45MzUgOTcuMjU3MiAxOTEuOTM1IDk2LjgyMDNaTTE5OS4zNzIgOTUuNDY2OFY5Ni42NTkyQzE5OS4zNzIgOTcuMzAwMSAxOTkuMzE0IDk3Ljg0MDggMTk5LjIgOTguMjgxMkMxOTkuMDg1IDk4LjcyMTcgMTk4LjkyIDk5LjA3NjIgMTk4LjcwNiA5OS4zNDQ3QzE5OC40OTEgOTkuNjEzMyAxOTguMjMxIDk5LjgwODQgMTk3LjkyNyA5OS45MzAyQzE5Ny42MjYgMTAwLjA0OCAxOTcuMjg2IDEwMC4xMDcgMTk2LjkwNiAxMDAuMTA3QzE5Ni42MDUgMTAwLjEwNyAxOTYuMzI4IDEwMC4wNyAxOTYuMDc0IDk5Ljk5NDZDMTk1LjgxOSA5OS45MTk0IDE5NS41OSA5OS43OTk1IDE5NS4zODYgOTkuNjM0OEMxOTUuMTg2IDk5LjQ2NjUgMTk1LjAxNCA5OS4yNDggMTk0Ljg3MSA5OC45Nzk1QzE5NC43MjcgOTguNzEwOSAxOTQuNjE4IDk4LjM4NTEgMTk0LjU0MyA5OC4wMDJDMTk0LjQ2OCA5Ny42MTg4IDE5NC40MyA5Ny4xNzEyIDE5NC40MyA5Ni42NTkyVjk1LjQ2NjhDMTk0LjQzIDk0LjgyNTggMTk0LjQ4NyA5NC4yODg3IDE5NC42MDIgOTMuODU1NUMxOTQuNzIgOTMuNDIyMiAxOTQuODg3IDkzLjA3NDkgMTk1LjEwMiA5Mi44MTM1QzE5NS4zMTYgOTIuNTQ4NSAxOTUuNTc0IDkyLjM1ODcgMTk1Ljg3NSA5Mi4yNDQxQzE5Ni4xNzkgOTIuMTI5NiAxOTYuNTIgOTIuMDcyMyAxOTYuODk2IDkyLjA3MjNDMTk3LjIgOTIuMDcyMyAxOTcuNDc5IDkyLjEwOTkgMTk3LjczMyA5Mi4xODUxQzE5Ny45OTEgOTIuMjU2NyAxOTguMjIgOTIuMzczIDE5OC40MjEgOTIuNTM0MkMxOTguNjIxIDkyLjY5MTcgMTk4Ljc5MiA5Mi45MDMgMTk4LjkzMSA5My4xNjhDMTk5LjA3NCA5My40Mjk0IDE5OS4xODQgOTMuNzQ5OCAxOTkuMjU5IDk0LjEyOTRDMTk5LjMzNCA5NC41MDkgMTk5LjM3MiA5NC45NTQ4IDE5OS4zNzIgOTUuNDY2OFpNMTk4LjM3MyA5Ni44MjAzVjk1LjMwMDNDMTk4LjM3MyA5NC45NDk0IDE5OC4zNTEgOTQuNjQxNCAxOTguMzA4IDk0LjM3NjVDMTk4LjI2OSA5NC4xMDc5IDE5OC4yMSA5My44Nzg3IDE5OC4xMzEgOTMuNjg5QzE5OC4wNTIgOTMuNDk5MiAxOTcuOTUyIDkzLjM0NTIgMTk3LjgzIDkzLjIyNzFDMTk3LjcxMiA5My4xMDg5IDE5Ny41NzQgOTMuMDIyOSAxOTcuNDE3IDkyLjk2OTJDMTk3LjI2MyA5Mi45MTE5IDE5Ny4wODkgOTIuODgzMyAxOTYuODk2IDkyLjg4MzNDMTk2LjY1OSA5Mi44ODMzIDE5Ni40NSA5Mi45MjgxIDE5Ni4yNjcgOTMuMDE3NkMxOTYuMDg0IDkzLjEwMzUgMTk1LjkzMSA5My4yNDE0IDE5NS44MDUgOTMuNDMxMkMxOTUuNjgzIDkzLjYyMDkgMTk1LjU5IDkzLjg2OTggMTk1LjUyNiA5NC4xNzc3QzE5NS40NjEgOTQuNDg1NyAxOTUuNDI5IDk0Ljg1OTkgMTk1LjQyOSA5NS4zMDAzVjk2LjgyMDNDMTk1LjQyOSA5Ny4xNzEyIDE5NS40NDkgOTcuNDgxIDE5NS40ODggOTcuNzQ5NUMxOTUuNTMxIDk4LjAxODEgMTk1LjU5NCA5OC4yNTA4IDE5NS42NzYgOTguNDQ3OEMxOTUuNzU5IDk4LjY0MTEgMTk1Ljg1OSA5OC44MDA1IDE5NS45NzcgOTguOTI1OEMxOTYuMDk1IDk5LjA1MTEgMTk2LjIzMSA5OS4xNDQyIDE5Ni4zODUgOTkuMjA1MUMxOTYuNTQzIDk5LjI2MjQgMTk2LjcxNiA5OS4yOTEgMTk2LjkwNiA5OS4yOTFDMTk3LjE1IDk5LjI5MSAxOTcuMzYzIDk5LjI0NDUgMTk3LjU0NSA5OS4xNTE0QzE5Ny43MjggOTkuMDU4MyAxOTcuODggOTguOTEzMiAxOTguMDAyIDk4LjcxNjNDMTk4LjEyNyA5OC41MTU4IDE5OC4yMiA5OC4yNTk4IDE5OC4yODEgOTcuOTQ4MkMxOTguMzQyIDk3LjYzMzEgMTk4LjM3MyA5Ny4yNTcyIDE5OC4zNzMgOTYuODIwM1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iODMiIHI9IjgiIGZpbGw9IiM1NDY5RkYiLz4KPC9zdmc+Cg==", + "description": "Allows users to move thumb of a slider to send commands to devices or update attributes/time-series data. Configurable settings let users define how to retrieve the initial state and specify actions for the value change.", + "descriptor": { + "type": "rpc", + "sizeX": 5, + "sizeY": 3, + "resources": [], + "templateHtml": "\n", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '360px',\n previewHeight: '220px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "", + "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-slider-widget-settings", + "hasBasicMode": true, + "basicModeDirective": "tb-slider-basic-config", + "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Slider\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"actions\":{},\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":null,\"weight\":\"500\",\"style\":null,\"lineHeight\":\"24px\"},\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"pageSize\":1024,\"titleIcon\":\"\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"14px\",\"configMode\":\"basic\",\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}" + }, + "tags": [ + "command", + "downlink", + "device configuration", + "device control", + "invocation", + "remote method", + "remote function", + "interface", + "subroutine call", + "inter-process communication", + "server request", + "update attribute", + "set attribute", + "add time-series", + "slider" + ] +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts index c268be7bcd..32a6a755b4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts @@ -103,6 +103,7 @@ import { import { PowerButtonBasicConfigComponent } from '@home/components/widget/config/basic/button/power-button-basic-config.component'; +import { SliderBasicConfigComponent } from '@home/components/widget/config/basic/rpc/slider-basic-config.component'; @NgModule({ declarations: [ @@ -135,7 +136,8 @@ import { SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, CommandButtonBasicConfigComponent, - PowerButtonBasicConfigComponent + PowerButtonBasicConfigComponent, + SliderBasicConfigComponent ], imports: [ CommonModule, @@ -172,7 +174,8 @@ import { SingleSwitchBasicConfigComponent, ActionButtonBasicConfigComponent, CommandButtonBasicConfigComponent, - PowerButtonBasicConfigComponent + PowerButtonBasicConfigComponent, + SliderBasicConfigComponent ] }) export class BasicWidgetConfigModule { @@ -203,5 +206,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type
    -
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    @@ -134,7 +134,7 @@
    -
    +
    {{ 'widgets.power-button.power-off-colors' | translate }}
    @@ -152,7 +152,7 @@
    -
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 18fa3b5587..c6ac3abb45 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -82,7 +82,7 @@ {{ 'widgets.single-switch.auto-scale' | translate }}
    -
    +
    {{ 'widgets.single-switch.label' | translate }} @@ -99,7 +99,7 @@
    -
    +
    {{ 'widgets.single-switch.icon' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html new file mode 100644 index 0000000000..76c4ff9ea3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html @@ -0,0 +1,270 @@ + + + +
    +
    widgets.slider.behavior
    +
    +
    widgets.slider.initial-value
    + +
    +
    +
    widgets.slider.on-value-change
    + +
    +
    +
    widgets.rpc-state.disabled-state
    + +
    +
    +
    +
    widget-config.appearance
    + + + {{ sliderLayoutTranslationMap.get(layout) | translate }} + + +
    + + {{ 'widgets.slider.auto-scale' | translate }} + +
    +
    + + {{ 'widget-config.title' | translate }} + +
    + + + + + + + +
    +
    +
    + + {{ 'widgets.slider.icon' | translate }} + +
    + + + + + + + + +
    +
    +
    + + {{ 'widgets.slider.value' | translate }} + +
    + + + +
    widget-config.decimals-suffix
    +
    + + + + +
    +
    +
    +
    {{ 'widgets.slider.range' | translate }}
    +
    +
    widgets.slider.min
    + + + +
    widgets.slider.max
    + + + +
    +
    +
    + + {{ 'widgets.slider.range-ticks' | translate }} + +
    + + + + +
    +
    +
    + + {{ 'widgets.slider.tick-marks' | translate }} + +
    + + + + + +
    +
    +
    +
    {{ 'widgets.slider.colors' | translate }}
    +
    +
    +
    widgets.slider.main
    + + +
    + +
    +
    widgets.slider.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.rpc-state.disabled-state' | translate }}
    +
    +
    +
    widgets.slider.main
    + + +
    + +
    +
    widgets.slider.background
    + + +
    +
    +
    +
    +
    + {{ 'widgets.slider.left-icon' | translate }} +
    +
    + + + + + + + + +
    +
    +
    +
    + {{ 'widgets.slider.right-icon' | translate }} +
    +
    + + + + + + + + +
    +
    +
    +
    +
    widget-config.card-appearance
    +
    +
    {{ 'widgets.background.background' | translate }}
    + + +
    +
    +
    widget-config.show-card-buttons
    + + {{ 'fullscreen.fullscreen' | translate }} + +
    +
    +
    {{ 'widget-config.card-border-radius' | translate }}
    + + + +
    +
    + + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts new file mode 100644 index 0000000000..b80c6acb08 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.ts @@ -0,0 +1,309 @@ +/// +/// 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 } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; +import { WidgetConfigComponentData } from '@home/models/widget-component.models'; +import { TargetDevice, WidgetConfig, } from '@shared/models/widget.models'; +import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { formatValue, isUndefined } from '@core/utils'; +import { ValueType } from '@shared/models/constants'; +import { + SliderLayout, + sliderLayoutImages, + sliderLayouts, + sliderLayoutTranslations, + sliderWidgetDefaultSettings, + SliderWidgetSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; + +@Component({ + selector: 'tb-slider-basic-config', + templateUrl: './slider-basic-config.component.html', + styleUrls: ['../basic-config.scss'] +}) +export class SliderBasicConfigComponent extends BasicWidgetConfigComponent { + + get targetDevice(): TargetDevice { + return this.sliderWidgetConfigForm.get('targetDevice').value; + } + + sliderLayout = SliderLayout; + + sliderLayouts = sliderLayouts; + + sliderLayoutTranslationMap = sliderLayoutTranslations; + sliderLayoutImageMap = sliderLayoutImages; + + valueType = ValueType; + + sliderWidgetConfigForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + protected widgetConfigComponent: WidgetConfigComponent, + private fb: UntypedFormBuilder) { + super(store, widgetConfigComponent); + } + + protected configForm(): UntypedFormGroup { + return this.sliderWidgetConfigForm; + } + + protected onConfigSet(configData: WidgetConfigComponentData) { + const settings: SliderWidgetSettings = {...sliderWidgetDefaultSettings, ...(configData.config.settings || {})}; + const iconSize = resolveCssSize(configData.config.iconSize); + this.sliderWidgetConfigForm = this.fb.group({ + targetDevice: [configData.config.targetDevice, []], + + initialState: [settings.initialState, []], + valueChange: [settings.valueChange, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + autoScale: [settings.autoScale, []], + + showTitle: [configData.config.showTitle, []], + title: [configData.config.title, []], + titleFont: [configData.config.titleFont, []], + titleColor: [configData.config.titleColor, []], + + showIcon: [configData.config.showTitleIcon, []], + iconSize: [iconSize[0], [Validators.min(0)]], + iconSizeUnit: [iconSize[1], []], + icon: [configData.config.titleIcon, []], + iconColor: [configData.config.iconColor, []], + + showValue: [settings.showValue, []], + valueUnits: [settings.valueUnits, []], + valueDecimals: [settings.valueDecimals, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + tickMin: [settings.tickMin, []], + tickMax: [settings.tickMax, []], + + showTicks: [settings.showTicks, []], + ticksFont: [settings.ticksFont, []], + ticksColor: [settings.ticksColor, []], + + showTickMarks: [settings.showTickMarks, []], + tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], + tickMarksColor: [settings.tickMarksColor, []], + + mainColor: [settings.mainColor, []], + backgroundColor: [settings.backgroundColor, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + leftIconSize: [settings.leftIconSize, [Validators.min(0)]], + leftIconSizeUnit: [settings.leftIconSizeUnit, []], + leftIcon: [settings.leftIcon, []], + leftIconColor: [settings.leftIconColor, []], + + rightIconSize: [settings.rightIconSize, [Validators.min(0)]], + rightIconSizeUnit: [settings.rightIconSizeUnit, []], + rightIcon: [settings.rightIcon, []], + rightIconColor: [settings.rightIconColor, []], + + background: [settings.background, []], + + cardButtons: [this.getCardButtons(configData.config), []], + borderRadius: [configData.config.borderRadius, []], + + actions: [configData.config.actions || {}, []] + }); + } + + protected prepareOutputConfig(config: any): WidgetConfigComponentData { + this.widgetConfig.config.targetDevice = config.targetDevice; + + this.widgetConfig.config.showTitle = config.showTitle; + this.widgetConfig.config.title = config.title; + this.widgetConfig.config.titleFont = config.titleFont; + this.widgetConfig.config.titleColor = config.titleColor; + + this.widgetConfig.config.showTitleIcon = config.showIcon; + this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); + this.widgetConfig.config.titleIcon = config.icon; + this.widgetConfig.config.iconColor = config.iconColor; + + this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + + this.widgetConfig.config.settings.initialState = config.initialState; + this.widgetConfig.config.settings.valueChange = config.valueChange; + this.widgetConfig.config.settings.disabledState = config.disabledState; + + this.widgetConfig.config.settings.layout = config.layout; + this.widgetConfig.config.settings.autoScale = config.autoScale; + + this.widgetConfig.config.settings.showValue = config.showValue; + this.widgetConfig.config.settings.valueUnits = config.valueUnits; + this.widgetConfig.config.settings.valueDecimals = config.valueDecimals; + this.widgetConfig.config.settings.valueFont = config.valueFont; + this.widgetConfig.config.settings.valueColor = config.valueColor; + + this.widgetConfig.config.settings.tickMin = config.tickMin; + this.widgetConfig.config.settings.tickMax = config.tickMax; + + this.widgetConfig.config.settings.showTicks = config.showTicks; + this.widgetConfig.config.settings.ticksFont = config.ticksFont; + this.widgetConfig.config.settings.ticksColor = config.ticksColor; + + this.widgetConfig.config.settings.showTickMarks = config.showTickMarks; + this.widgetConfig.config.settings.tickMarksCount = config.tickMarksCount; + this.widgetConfig.config.settings.tickMarksColor = config.tickMarksColor; + + this.widgetConfig.config.settings.mainColor = config.mainColor; + this.widgetConfig.config.settings.backgroundColor = config.backgroundColor; + + this.widgetConfig.config.settings.mainColorDisabled = config.mainColorDisabled; + this.widgetConfig.config.settings.backgroundColorDisabled = config.backgroundColorDisabled; + + this.widgetConfig.config.settings.leftIconSize = config.leftIconSize; + this.widgetConfig.config.settings.leftIconSizeUnit = config.leftIconSizeUnit; + this.widgetConfig.config.settings.leftIcon = config.leftIcon; + this.widgetConfig.config.settings.leftIconColor = config.leftIconColor; + + this.widgetConfig.config.settings.rightIconSize = config.rightIconSize; + this.widgetConfig.config.settings.rightIconSizeUnit = config.rightIconSizeUnit; + this.widgetConfig.config.settings.rightIcon = config.rightIcon; + this.widgetConfig.config.settings.rightIconColor = config.rightIconColor; + + this.widgetConfig.config.settings.background = config.background; + + this.setCardButtons(config.cardButtons, this.widgetConfig.config); + this.widgetConfig.config.borderRadius = config.borderRadius; + + this.widgetConfig.config.actions = config.actions; + return this.widgetConfig; + } + + protected validatorTriggers(): string[] { + return ['showTitle', 'showIcon', 'showValue', 'showTicks', 'showTickMarks', 'layout']; + } + + protected updateValidators(emitEvent: boolean, trigger?: string) { + const showTitle: boolean = this.sliderWidgetConfigForm.get('showTitle').value; + const showIcon: boolean = this.sliderWidgetConfigForm.get('showIcon').value; + const showValue: boolean = this.sliderWidgetConfigForm.get('showValue').value; + const showTicks: boolean = this.sliderWidgetConfigForm.get('showTicks').value; + const showTickMarks: boolean = this.sliderWidgetConfigForm.get('showTickMarks').value; + const layout: SliderLayout = this.sliderWidgetConfigForm.get('layout').value; + + const valueEnabled = layout !== SliderLayout.simplified; + const leftRightIconsEnabled = layout === SliderLayout.extended; + + if (showTitle) { + this.sliderWidgetConfigForm.get('title').enable(); + this.sliderWidgetConfigForm.get('titleFont').enable(); + this.sliderWidgetConfigForm.get('titleColor').enable(); + this.sliderWidgetConfigForm.get('showIcon').enable({emitEvent: false}); + if (showIcon) { + this.sliderWidgetConfigForm.get('iconSize').enable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('icon').enable(); + this.sliderWidgetConfigForm.get('iconColor').enable(); + } else { + this.sliderWidgetConfigForm.get('iconSize').disable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('icon').disable(); + this.sliderWidgetConfigForm.get('iconColor').disable(); + } + } else { + this.sliderWidgetConfigForm.get('title').disable(); + this.sliderWidgetConfigForm.get('titleFont').disable(); + this.sliderWidgetConfigForm.get('titleColor').disable(); + this.sliderWidgetConfigForm.get('showIcon').disable({emitEvent: false}); + this.sliderWidgetConfigForm.get('iconSize').disable(); + this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('icon').disable(); + this.sliderWidgetConfigForm.get('iconColor').disable(); + } + + if (valueEnabled && showValue) { + this.sliderWidgetConfigForm.get('valueUnits').enable(); + this.sliderWidgetConfigForm.get('valueDecimals').enable(); + this.sliderWidgetConfigForm.get('valueFont').enable(); + this.sliderWidgetConfigForm.get('valueColor').enable(); + } else { + this.sliderWidgetConfigForm.get('valueUnits').disable(); + this.sliderWidgetConfigForm.get('valueDecimals').disable(); + this.sliderWidgetConfigForm.get('valueFont').disable(); + this.sliderWidgetConfigForm.get('valueColor').disable(); + } + + if (showTicks) { + this.sliderWidgetConfigForm.get('ticksFont').enable(); + this.sliderWidgetConfigForm.get('ticksColor').enable(); + } else { + this.sliderWidgetConfigForm.get('ticksFont').disable(); + this.sliderWidgetConfigForm.get('ticksColor').disable(); + } + + if (showTickMarks) { + this.sliderWidgetConfigForm.get('tickMarksCount').enable(); + this.sliderWidgetConfigForm.get('tickMarksColor').enable(); + } else { + this.sliderWidgetConfigForm.get('tickMarksCount').disable(); + this.sliderWidgetConfigForm.get('tickMarksColor').disable(); + } + + if (leftRightIconsEnabled) { + this.sliderWidgetConfigForm.get('leftIconSize').enable(); + this.sliderWidgetConfigForm.get('leftIconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('leftIcon').enable(); + this.sliderWidgetConfigForm.get('leftIconColor').enable(); + this.sliderWidgetConfigForm.get('rightIconSize').enable(); + this.sliderWidgetConfigForm.get('rightIconSizeUnit').enable(); + this.sliderWidgetConfigForm.get('rightIcon').enable(); + this.sliderWidgetConfigForm.get('rightIconColor').enable(); + } else { + this.sliderWidgetConfigForm.get('leftIconSize').disable(); + this.sliderWidgetConfigForm.get('leftIconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('leftIcon').disable(); + this.sliderWidgetConfigForm.get('leftIconColor').disable(); + this.sliderWidgetConfigForm.get('rightIconSize').disable(); + this.sliderWidgetConfigForm.get('rightIconSizeUnit').disable(); + this.sliderWidgetConfigForm.get('rightIcon').disable(); + this.sliderWidgetConfigForm.get('rightIconColor').disable(); + } + } + + private getCardButtons(config: WidgetConfig): string[] { + const buttons: string[] = []; + if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { + buttons.push('fullscreen'); + } + return buttons; + } + + private setCardButtons(buttons: string[], config: WidgetConfig) { + config.enableFullscreen = buttons.includes('fullscreen'); + } + + private _valuePreviewFn(): string { + const units: string = this.sliderWidgetConfigForm.get('valueUnits').value; + const decimals: number = this.sliderWidgetConfigForm.get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index dc56a1e992..eb22e2a014 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -62,8 +62,6 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A loading$ = this.loadingSubject.asObservable().pipe(share()); - error = ''; - protected constructor(protected cd: ChangeDetectorRef) { } @@ -99,8 +97,7 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } public clearError() { - this.error = ''; - this.cd.markForCheck(); + this.ctx.hideToast(this.ctx.toastTargetId); } protected createValueGetter(getValueSettings: GetValueSettings, @@ -113,7 +110,8 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } }, error: (err: any) => { - this.onError(err); + const message = parseError(this.ctx, err); + this.onError(message); if (valueObserver?.error) { valueObserver.error(err); } @@ -132,8 +130,7 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } private onError(error: string) { - this.error = error; - this.cd.markForCheck(); + this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true); } protected updateValue(valueSetter: ValueSetter, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss index 6641c2c32e..081cb00f0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.scss @@ -19,34 +19,3 @@ left: 0; right: 0; } -.tb-action-widget-error-container { - position: absolute; - bottom: 0; - left: 0; - right: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1; - .tb-action-widget-error-panel { - display: flex; - padding: 4px 4px 4px 12px; - justify-content: center; - align-items: center; - gap: 4px; - border-radius: 4px; - background-color: #fff2f3; - box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); - .tb-action-widget-error-text { - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 16px; - color: rgba(209, 39, 48, 1); - } - .tb-action-widget-error-clear { - color: rgba(209, 39, 48, 1); - } - } -} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html index 02e2406859..294ba855fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/action-button-widget.component.html @@ -27,10 +27,4 @@ [ctx]="ctx" (clicked)="onClick($event)"> -
    -
    -
    - -
    -
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html index 26f910fdd7..d48ba2f5e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/button/command-button-widget.component.html @@ -27,10 +27,4 @@ (clicked)="onClick($event)"> -
    -
    -
    - -
    -
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html index 93e394ac5e..48e4106a97 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.html @@ -23,10 +23,4 @@
    -
    -
    -
    - -
    -
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html index c1c05cf880..fd9aaca4a6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/single-switch-widget.component.html @@ -37,10 +37,4 @@
    -
    -
    -
    - -
    -
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html new file mode 100644 index 0000000000..f15892dd52 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.html @@ -0,0 +1,59 @@ + +
    +
    +
    + +
    +
    +
    +
    {{ valueText }}
    +
    +
    +
    + {{ leftIcon }} +
    +
    + + + +
    +
    +
    {{ settings.tickMin }}
    +
    +
    +
    {{ settings.tickMax }}
    +
    +
    +
    +
    + {{ rightIcon }} +
    +
    +
    + +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss new file mode 100644 index 0000000000..0d7115fccb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.scss @@ -0,0 +1,135 @@ +/** + * 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. + */ +$mainColor: var(--tb-slider-main-color, #5469FF); +$hoverRippleColor: var(--tb-slider-hover-ripple-color, rgba(84, 105, 255, 0.05)); +$focusRippleColor: var(--tb-slider-focus-ripple-color, rgba(84, 105, 255, 0.2)); +$backgroundColor: var(--tb-slider-background-color, #CCD2FF); +$tickMarksColor: var(--tb-slider-tick-marks-color, #5469FF); + +$mainColorDisabled: var(--tb-slider-main-color-disabled, #9BA2B0); +$backgroundColorDisabled: var(--tb-slider-background-color-disabled, #D5D7E5); + +.tb-slider-panel { + + .mat-mdc-slider.mat-primary.tb-slider { + --mdc-slider-active-track-color: #{$mainColor}; + --mdc-slider-handle-color: #{$mainColor}; + --mdc-slider-focus-handle-color: #{$mainColor}; + --mdc-slider-hover-handle-color: #{$mainColor}; + + --mdc-slider-with-tick-marks-inactive-container-color: #{$tickMarksColor}; + --mat-mdc-slider-ripple-color: #{$mainColor}; + + --mat-mdc-slider-hover-ripple-color: #{$hoverRippleColor}; + --mat-mdc-slider-focus-ripple-color: #{$focusRippleColor}; + + --mdc-slider-inactive-track-color: #{$backgroundColor}; + + --mdc-slider-disabled-active-track-color: #{$mainColorDisabled}; + --mdc-slider-disabled-handle-color: #{$mainColorDisabled}; + + --mdc-slider-disabled-inactive-track-color: #{$backgroundColorDisabled}; + --mdc-slider-with-tick-marks-disabled-container-color: #{$mainColorDisabled}; + + --mdc-slider-handle-width: 16px; + --mdc-slider-handle-height: 16px; + } + + width: 100%; + height: 100%; + position: relative; + display: flex; + flex-direction: column; + padding: 20px 24px 24px 24px; + gap: 8px; + > div:not(.tb-slider-overlay), > tb-icon { + z-index: 1; + } + .tb-slider-overlay { + position: absolute; + inset: 12px; + } + div.tb-slider-title-panel { + z-index: 2; + } + .tb-slider-content { + flex: 1; + min-height: 0; + display: flex; + position: relative; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + .tb-slider-value-container { + min-height: 0; + } + .tb-slider-value { + white-space: nowrap; + } + .tb-slider-container { + align-self: stretch; + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 8px; + &.tb-min-height { + height: 6px; + } + .tb-slider-column { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + .mat-mdc-slider.tb-slider { + margin: 0; + height: 6px; + min-height: 6px; + min-width: 0; + &.mdc-slider--disabled { + opacity: 1; + } + .mdc-slider__track--inactive { + opacity: 1; + } + .mdc-slider__tick-marks { + .mdc-slider__tick-mark--active { + display: none; + } + .mdc-slider__tick-mark--inactive { + opacity: 1; + } + } + .mdc-slider__thumb.mat-mdc-slider-visual-thumb { + top: -21px; + .mat-ripple { + overflow: visible; + } + } + .mdc-slider__value-indicator-text { + white-space: nowrap; + } + } + .tb-slider-ticks { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts new file mode 100644 index 0000000000..a60cfd84a0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts @@ -0,0 +1,347 @@ +/// +/// 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 { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; +import { + backgroundStyle, + ComponentStyle, + iconStyle, + overlayStyle, + textStyle +} from '@shared/models/widget-settings.models'; +import { Observable } from 'rxjs'; +import { ResizeObserver } from '@juggle/resize-observer'; +import { ImagePipe } from '@shared/pipe/image.pipe'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ValueType } from '@shared/models/constants'; +import { UtilsService } from '@core/services/utils.service'; +import { + SliderLayout, + sliderWidgetDefaultSettings, + SliderWidgetSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; +import { WidgetComponent } from '@home/components/widget/widget.component'; +import tinycolor from 'tinycolor2'; + +@Component({ + selector: 'tb-slider-widget', + templateUrl: './slider-widget.component.html', + styleUrls: ['../action/action-widget.scss', './slider-widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SliderWidgetComponent extends + BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('sliderContent', {static: false}) + sliderContent: ElementRef; + + @ViewChild('sliderValueContainer', {static: false}) + sliderValueContainer: ElementRef; + + @ViewChild('sliderValue', {static: false}) + sliderValue: ElementRef; + + @ViewChild('sliderTickMinContainer', {static: false}) + sliderTickMinContainer: ElementRef; + + @ViewChild('sliderTickMin', {static: false}) + sliderTickMin: ElementRef; + + @ViewChild('sliderTickMaxContainer', {static: false}) + sliderTickMaxContainer: ElementRef; + + @ViewChild('sliderTickMax', {static: false}) + sliderTickMax: ElementRef; + + @ViewChild('leftSliderIconContainer', {static: false, read: ElementRef}) + leftSliderIconContainer: ElementRef; + + @ViewChild('leftSliderIcon', {static: false, read: ElementRef}) + leftSliderIcon: ElementRef; + + @ViewChild('rightSliderIconContainer', {static: false, read: ElementRef}) + rightSliderIconContainer: ElementRef; + + @ViewChild('rightSliderIcon', {static: false, read: ElementRef}) + rightSliderIcon: ElementRef; + + settings: SliderWidgetSettings; + + backgroundStyle$: Observable; + overlayStyle: ComponentStyle = {}; + + value: number = null; + private prevValue: number = null; + + disabled = false; + + layout: SliderLayout; + + showValue = true; + valueText = 'N/A'; + valueStyle: ComponentStyle = {}; + + showLeftRightIcon = false; + leftIcon = ''; + leftIconStyle: ComponentStyle = {}; + rightIcon = ''; + rightIconStyle: ComponentStyle = {}; + + showTicks = true; + ticksStyle: ComponentStyle = {}; + + sliderStep: number = undefined; + + autoScale = false; + + showWidgetTitlePanel = this.widgetComponent.dashboardWidget.showWidgetTitlePanel; + + sliderValueText = this._sliderValueText.bind(this); + + private panelResize$: ResizeObserver; + + private valueSetter: ValueSetter; + + private sliderCssClass: string; + + constructor(protected imagePipe: ImagePipe, + protected sanitizer: DomSanitizer, + private renderer: Renderer2, + private utils: UtilsService, + private widgetComponent: WidgetComponent, + protected cd: ChangeDetectorRef, + private elementRef: ElementRef) { + super(cd); + } + + ngOnInit(): void { + super.ngOnInit(); + this.settings = {...sliderWidgetDefaultSettings, ...this.ctx.settings}; + + this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); + this.overlayStyle = overlayStyle(this.settings.background.overlay); + + this.layout = this.settings.layout; + + this.autoScale = this.settings.autoScale; + + this.showValue = this.layout !== SliderLayout.simplified && this.settings.showValue; + this.valueStyle = textStyle(this.settings.valueFont); + this.valueStyle.color = this.settings.valueColor; + + this.showLeftRightIcon = this.layout === SliderLayout.extended; + if (this.showLeftRightIcon) { + this.leftIcon = this.settings.leftIcon; + this.leftIconStyle = iconStyle(this.settings.leftIconSize, this.settings.leftIconSizeUnit ); + this.rightIcon = this.settings.rightIcon; + this.rightIconStyle = iconStyle(this.settings.rightIconSize, this.settings.rightIconSizeUnit ); + if (!this.autoScale) { + const leftIconMargin = this.settings.leftIconSize / 2 + (this.settings.leftIconSizeUnit || 'px'); + this.leftIconStyle.marginTop = `calc(-${leftIconMargin} + 3px)`; + const rightIconMargin = this.settings.rightIconSize / 2 + (this.settings.rightIconSizeUnit || 'px'); + this.rightIconStyle.marginTop = `calc(-${rightIconMargin} + 3px)`; + } + } + + this.showTicks = this.settings.showTicks; + if (this.showTicks) { + this.ticksStyle = textStyle(this.settings.ticksFont); + this.ticksStyle.color = this.settings.ticksColor; + } + + if (this.settings.showTickMarks) { + const range = this.settings.tickMax - this.settings.tickMin; + this.sliderStep = Math.floor(range / (this.settings.tickMarksCount - 1)); + } + + const mainColorInstance = tinycolor(this.settings.mainColor); + const hoverRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.05).toRgbString(); + const focusRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.2).toRgbString(); + + const sliderVariablesCss = `.tb-slider-panel {\n`+ + `--tb-slider-main-color: ${this.settings.mainColor};\n`+ + `--tb-slider-background-color: ${this.settings.backgroundColor};\n`+ + `--tb-slider-hover-ripple-color: ${hoverRippleColor};\n`+ + `--tb-slider-focus-ripple-color: ${focusRippleColor};\n`+ + `--tb-slider-tick-marks-color: ${this.settings.tickMarksColor};\n`+ + `--tb-slider-main-color-disabled: ${this.settings.mainColorDisabled};\n`+ + `--tb-slider-background-disabled: ${this.settings.backgroundColorDisabled};\n`+ + `}`; + this.sliderCssClass = + this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-slider', sliderVariablesCss); + + const getInitialStateSettings = + {...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')}; + this.createValueGetter(getInitialStateSettings, ValueType.INTEGER, { + next: (value) => this.onValue(value) + }); + + const disabledStateSettings = + {...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; + this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { + next: (value) => this.onDisabled(value) + }); + + const valueChangeSettings = {...this.settings.valueChange, + actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; + this.valueSetter = this.createValueSetter(valueChangeSettings); + } + + ngAfterViewInit(): void { + if (this.autoScale) { + this.panelResize$ = new ResizeObserver(() => { + this.onResize(); + }); + this.panelResize$.observe(this.sliderContent.nativeElement); + if (this.showValue) { + this.panelResize$.observe(this.sliderValueContainer.nativeElement); + } + this.onResize(); + } + super.ngAfterViewInit(); + } + + ngOnDestroy() { + if (this.panelResize$) { + this.panelResize$.disconnect(); + } + if (this.sliderCssClass) { + this.utils.clearCssElement(this.renderer, this.sliderCssClass); + } + super.ngOnDestroy(); + } + + public onInit() { + super.onInit(); + const borderRadius = this.ctx.$widgetElement.css('borderRadius'); + this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; + this.cd.detectChanges(); + } + + public onSliderChange() { + this.updateValueText(); + if (!this.ctx.isEdit && !this.ctx.isPreview) { + const prevValue = this.prevValue; + const targetValue = this.value; + this.updateValue(this.valueSetter, targetValue, { + next: () => this.onValue(targetValue), + error: () => this.onValue(prevValue) + }); + } + } + + private _sliderValueText(value: number): string { + return formatValue(value, this.settings.valueDecimals, this.settings.valueUnits, false); + } + + private onValue(value: number): void { + this.value = value; + this.prevValue = value; + this.updateValueText(); + this.cd.markForCheck(); + } + + private updateValueText() { + if (isDefinedAndNotNull(this.value) && isNumeric(this.value)) { + this.valueText = formatValue(this.value, this.settings.valueDecimals, this.settings.valueUnits, false); + } else { + this.valueText = 'N/A'; + } + } + + private onDisabled(value: boolean): void { + this.disabled = !!value; + this.cd.markForCheck(); + } + + private onResize() { + const panelWidth = this.sliderContent.nativeElement.getBoundingClientRect().width; + const panelHeight = this.sliderContent.nativeElement.getBoundingClientRect().height; + + if (this.showValue) { + this.resetScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement); + } + + if (this.showLeftRightIcon) { + this.resetScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement); + this.resetScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement); + } + + if (this.showTicks) { + this.resetScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement); + this.resetScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement); + } + + let minAspect = 0.2; + let avgContentHeight = 45; + if (this.showTicks) { + minAspect += 0.1; + avgContentHeight += 20; + } + if (this.showValue) { + minAspect += 0.1; + avgContentHeight += 50; + } + const aspect = Math.min(panelHeight / panelWidth, minAspect); + const targetHeight = panelWidth * aspect; + const scale = targetHeight / avgContentHeight; + + if (this.showValue) { + this.updateScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement, scale); + } + if (this.showLeftRightIcon) { + const leftIconContainerRect = this.leftSliderIconContainer.nativeElement.getBoundingClientRect(); + const leftIconContainerMarginTop = -(leftIconContainerRect.width * scale) / 2 + 3; + this.renderer.setStyle(this.leftSliderIconContainer.nativeElement, 'marginTop', `${leftIconContainerMarginTop}px`); + this.updateScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement, scale, true); + const rightIconContainerRect = this.rightSliderIconContainer.nativeElement.getBoundingClientRect(); + const rightIconContainerMarginTop = -(rightIconContainerRect.width * scale) / 2 + 3; + this.renderer.setStyle(this.rightSliderIconContainer.nativeElement, 'marginTop', `${rightIconContainerMarginTop}px`); + this.updateScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement, scale, true); + } + if (this.showTicks) { + this.updateScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement, scale); + this.updateScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement, scale); + } + } + + private resetScale(container: HTMLElement, element: HTMLElement): void { + this.renderer.setStyle(container, 'width', ''); + this.renderer.setStyle(container, 'height', ''); + this.renderer.setStyle(element, 'transform', ''); + } + + private updateScale(container: HTMLElement, element: HTMLElement, scale: number, sameHeight = false): void { + const rect = container.getBoundingClientRect(); + this.renderer.setStyle(container, 'width', `${rect.width * scale}px`); + this.renderer.setStyle(container, 'height', `${(sameHeight ? rect.width : rect.height) * scale}px`); + this.renderer.setStyle(element, 'transform', `scale(${scale})`); + this.renderer.setStyle(element, 'transform-origin', 'left top'); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts new file mode 100644 index 0000000000..78603bc058 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts @@ -0,0 +1,196 @@ +/// +/// 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 { + DataToValueType, + GetValueAction, + GetValueSettings, + SetValueAction, + SetValueSettings, + ValueToDataType +} from '@shared/models/action-widget-settings.models'; +import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; +import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; + +export enum SliderLayout { + default = 'default', + extended = 'extended', + simplified = 'simplified' +} + +export const sliderLayouts = Object.keys(SliderLayout) as SliderLayout[]; + +export const sliderLayoutTranslations = new Map( + [ + [SliderLayout.default, 'widgets.slider.layout-default'], + [SliderLayout.extended, 'widgets.slider.layout-extended'], + [SliderLayout.simplified, 'widgets.slider.layout-simplified'] + ] +); + +export const sliderLayoutImages = new Map( + [ + [SliderLayout.default, 'assets/widget/slider/default-layout.svg'], + [SliderLayout.extended, 'assets/widget/slider/extended-layout.svg'], + [SliderLayout.simplified, 'assets/widget/slider/simplified-layout.svg'] + ] +); + +export interface SliderWidgetSettings { + initialState: GetValueSettings; + disabledState: GetValueSettings; + valueChange: SetValueSettings; + layout: SliderLayout; + autoScale: boolean; + showValue: boolean; + valueUnits: string; + valueDecimals: number; + valueFont: Font; + valueColor: string; + showTicks: boolean; + tickMin: number; + tickMax: number; + ticksFont: Font; + ticksColor: string; + showTickMarks: boolean; + tickMarksCount: number; + tickMarksColor: string; + mainColor: string; + backgroundColor: string; + mainColorDisabled: string; + backgroundColorDisabled: string; + leftIcon: string; + leftIconSize: number; + leftIconSizeUnit: cssUnit; + leftIconColor: string; + rightIcon: string; + rightIconSize: number; + rightIconSizeUnit: cssUnit; + rightIconColor: string; + background: BackgroundSettings; +} + +export const sliderWidgetDefaultSettings: SliderWidgetSettings = { + initialState: { + action: GetValueAction.EXECUTE_RPC, + defaultValue: 0, + executeRpc: { + method: 'getState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return integer value */\nreturn data;' + } + }, + disabledState: { + action: GetValueAction.DO_NOTHING, + defaultValue: false, + getAttribute: { + key: 'state', + scope: null + }, + getTimeSeries: { + key: 'state' + }, + dataToValue: { + type: DataToValueType.NONE, + compareToValue: true, + dataToValueFunction: '/* Should return boolean value */\nreturn data;' + } + }, + valueChange: { + action: SetValueAction.EXECUTE_RPC, + executeRpc: { + method: 'setState', + requestTimeout: 5000, + requestPersistent: false, + persistentPollingInterval: 1000 + }, + setAttribute: { + key: 'state', + scope: AttributeScope.SHARED_SCOPE + }, + putTimeSeries: { + key: 'state' + }, + valueToData: { + type: ValueToDataType.FUNCTION, + constantValue: 0, + valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' + } + }, + layout: SliderLayout.default, + autoScale: true, + showValue: true, + valueUnits: '%', + valueDecimals: 0, + valueFont: { + family: 'Roboto', + size: 36, + sizeUnit: 'px', + style: 'normal', + weight: '500', + lineHeight: '36px' + }, + valueColor: 'rgba(0, 0, 0, 0.87)', + showTicks: true, + tickMin: 0, + tickMax: 100, + ticksFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '16px' + }, + ticksColor: 'rgba(0,0,0,0.54)', + showTickMarks: true, + tickMarksCount: 11, + tickMarksColor: '#5469FF', + mainColor: '#5469FF', + backgroundColor: '#CCD2FF', + mainColorDisabled: '#9BA2B0', + backgroundColorDisabled: '#D5D7E5', + leftIcon: 'lightbulb', + leftIconSize: 24, + leftIconSizeUnit: 'px', + leftIconColor: '#5469FF', + rightIcon: 'mdi:lightbulb-on', + rightIconSize: 24, + rightIconSizeUnit: 'px', + rightIconColor: '#5469FF', + background: { + type: BackgroundType.color, + color: '#fff', + overlay: { + enabled: false, + color: 'rgba(255,255,255,0.72)', + blur: 3 + } + } +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html index ced9bf9575..b37030c884 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/button/power-button-widget-settings.component.html @@ -84,7 +84,7 @@
    widgets.power-button.button
    -
    +
    {{ 'widgets.power-button.power-on-colors' | translate }}
    @@ -102,7 +102,7 @@
    -
    +
    {{ 'widgets.power-button.power-off-colors' | translate }}
    @@ -120,7 +120,7 @@
    -
    +
    {{ 'widgets.power-button.disabled-colors' | translate }}
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html index b4ce9c8ce2..bdecc9d36b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html @@ -56,7 +56,7 @@
    -
    +
    {{ 'widgets.button.color-palette' | translate }}
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html index 9350c2d932..9208efd68a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/single-switch-widget-settings.component.html @@ -81,7 +81,7 @@ {{ 'widgets.single-switch.auto-scale' | translate }}
    -
    +
    {{ 'widgets.single-switch.label' | translate }} @@ -98,7 +98,7 @@
    -
    +
    {{ 'widgets.single-switch.icon' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html new file mode 100644 index 0000000000..9ef4a603ec --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html @@ -0,0 +1,215 @@ + + +
    +
    widgets.slider.behavior
    +
    +
    widgets.slider.initial-value
    + +
    +
    +
    widgets.slider.on-value-change
    + +
    +
    +
    widgets.rpc-state.disabled-state
    + +
    +
    +
    +
    widget-config.card-style
    + + + {{ sliderLayoutTranslationMap.get(layout) | translate }} + + +
    + + {{ 'widgets.slider.auto-scale' | translate }} + +
    +
    +
    {{ 'widgets.background.background' | translate }}
    + + +
    +
    +
    +
    widgets.slider.slider
    +
    + + {{ 'widgets.slider.value' | translate }} + +
    + + + +
    widget-config.decimals-suffix
    +
    + + + + +
    +
    +
    +
    {{ 'widgets.slider.range' | translate }}
    +
    +
    widgets.slider.min
    + + + +
    widgets.slider.max
    + + + +
    +
    +
    + + {{ 'widgets.slider.range-ticks' | translate }} + +
    + + + + +
    +
    +
    + + {{ 'widgets.slider.tick-marks' | translate }} + +
    + + + + + +
    +
    +
    +
    {{ 'widgets.slider.colors' | translate }}
    +
    +
    +
    widgets.slider.main
    + + +
    + +
    +
    widgets.slider.background
    + + +
    +
    +
    +
    +
    {{ 'widgets.rpc-state.disabled-state' | translate }}
    +
    +
    +
    widgets.slider.main
    + + +
    + +
    +
    widgets.slider.background
    + + +
    +
    +
    +
    +
    + {{ 'widgets.slider.left-icon' | translate }} +
    +
    + + + + + + + + +
    +
    +
    +
    + {{ 'widgets.slider.right-icon' | translate }} +
    +
    + + + + + + + + +
    +
    +
    +
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts new file mode 100644 index 0000000000..a215a93d88 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.ts @@ -0,0 +1,186 @@ +/// +/// 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 } from '@angular/core'; +import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ValueType } from '@shared/models/constants'; +import { + SliderLayout, + sliderLayoutImages, + sliderLayouts, + sliderLayoutTranslations, + sliderWidgetDefaultSettings +} from '@home/components/widget/lib/rpc/slider-widget.models'; +import { formatValue } from '@core/utils'; + +@Component({ + selector: 'tb-slider-widget-settings', + templateUrl: './slider-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class SliderWidgetSettingsComponent extends WidgetSettingsComponent { + + get targetDevice(): TargetDevice { + return this.widgetConfig?.config?.targetDevice; + } + + get widgetType(): widgetType { + return this.widgetConfig?.widgetType; + } + + sliderLayout = SliderLayout; + + sliderLayouts = sliderLayouts; + + sliderLayoutTranslationMap = sliderLayoutTranslations; + sliderLayoutImageMap = sliderLayoutImages; + + valueType = ValueType; + + sliderWidgetSettingsForm: UntypedFormGroup; + + valuePreviewFn = this._valuePreviewFn.bind(this); + + constructor(protected store: Store, + private fb: UntypedFormBuilder) { + super(store); + } + + protected settingsForm(): UntypedFormGroup { + return this.sliderWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return {...sliderWidgetDefaultSettings}; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.sliderWidgetSettingsForm = this.fb.group({ + initialState: [settings.initialState, []], + valueChange: [settings.valueChange, []], + disabledState: [settings.disabledState, []], + + layout: [settings.layout, []], + autoScale: [settings.autoScale, []], + + showValue: [settings.showValue, []], + valueUnits: [settings.valueUnits, []], + valueDecimals: [settings.valueDecimals, []], + valueFont: [settings.valueFont, []], + valueColor: [settings.valueColor, []], + + tickMin: [settings.tickMin, []], + tickMax: [settings.tickMax, []], + + showTicks: [settings.showTicks, []], + ticksFont: [settings.ticksFont, []], + ticksColor: [settings.ticksColor, []], + + showTickMarks: [settings.showTickMarks, []], + tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], + tickMarksColor: [settings.tickMarksColor, []], + + mainColor: [settings.mainColor, []], + backgroundColor: [settings.backgroundColor, []], + + mainColorDisabled: [settings.mainColorDisabled, []], + backgroundColorDisabled: [settings.backgroundColorDisabled, []], + + leftIconSize: [settings.leftIconSize, [Validators.min(0)]], + leftIconSizeUnit: [settings.leftIconSizeUnit, []], + leftIcon: [settings.leftIcon, []], + leftIconColor: [settings.leftIconColor, []], + + rightIconSize: [settings.rightIconSize, [Validators.min(0)]], + rightIconSizeUnit: [settings.rightIconSizeUnit, []], + rightIcon: [settings.rightIcon, []], + rightIconColor: [settings.rightIconColor, []], + + background: [settings.background, []], + }); + } + + protected validatorTriggers(): string[] { + return ['showValue', 'showTicks', 'showTickMarks', 'layout']; + } + + protected updateValidators(_emitEvent: boolean): void { + const showValue: boolean = this.sliderWidgetSettingsForm.get('showValue').value; + const showTicks: boolean = this.sliderWidgetSettingsForm.get('showTicks').value; + const showTickMarks: boolean = this.sliderWidgetSettingsForm.get('showTickMarks').value; + const layout: SliderLayout = this.sliderWidgetSettingsForm.get('layout').value; + + const valueEnabled = layout !== SliderLayout.simplified; + const leftRightIconsEnabled = layout === SliderLayout.extended; + + if (valueEnabled && showValue) { + this.sliderWidgetSettingsForm.get('valueUnits').enable(); + this.sliderWidgetSettingsForm.get('valueDecimals').enable(); + this.sliderWidgetSettingsForm.get('valueFont').enable(); + this.sliderWidgetSettingsForm.get('valueColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('valueUnits').disable(); + this.sliderWidgetSettingsForm.get('valueDecimals').disable(); + this.sliderWidgetSettingsForm.get('valueFont').disable(); + this.sliderWidgetSettingsForm.get('valueColor').disable(); + } + + if (showTicks) { + this.sliderWidgetSettingsForm.get('ticksFont').enable(); + this.sliderWidgetSettingsForm.get('ticksColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('ticksFont').disable(); + this.sliderWidgetSettingsForm.get('ticksColor').disable(); + } + + if (showTickMarks) { + this.sliderWidgetSettingsForm.get('tickMarksCount').enable(); + this.sliderWidgetSettingsForm.get('tickMarksColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('tickMarksCount').disable(); + this.sliderWidgetSettingsForm.get('tickMarksColor').disable(); + } + + if (leftRightIconsEnabled) { + this.sliderWidgetSettingsForm.get('leftIconSize').enable(); + this.sliderWidgetSettingsForm.get('leftIconSizeUnit').enable(); + this.sliderWidgetSettingsForm.get('leftIcon').enable(); + this.sliderWidgetSettingsForm.get('leftIconColor').enable(); + this.sliderWidgetSettingsForm.get('rightIconSize').enable(); + this.sliderWidgetSettingsForm.get('rightIconSizeUnit').enable(); + this.sliderWidgetSettingsForm.get('rightIcon').enable(); + this.sliderWidgetSettingsForm.get('rightIconColor').enable(); + } else { + this.sliderWidgetSettingsForm.get('leftIconSize').disable(); + this.sliderWidgetSettingsForm.get('leftIconSizeUnit').disable(); + this.sliderWidgetSettingsForm.get('leftIcon').disable(); + this.sliderWidgetSettingsForm.get('leftIconColor').disable(); + this.sliderWidgetSettingsForm.get('rightIconSize').disable(); + this.sliderWidgetSettingsForm.get('rightIconSizeUnit').disable(); + this.sliderWidgetSettingsForm.get('rightIcon').disable(); + this.sliderWidgetSettingsForm.get('rightIconColor').disable(); + } + } + + private _valuePreviewFn(): string { + const units: string = this.sliderWidgetSettingsForm.get('valueUnits').value; + const decimals: number = this.sliderWidgetSettingsForm.get('valueDecimals').value; + return formatValue(48, decimals, units, false); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index d3fe9dac3e..8fc6ef96c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -324,6 +324,9 @@ import { import { PowerButtonWidgetSettingsComponent } from '@home/components/widget/lib/settings/button/power-button-widget-settings.component'; +import { + SliderWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/slider-widget-settings.component'; @NgModule({ declarations: [ @@ -440,7 +443,8 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, - PowerButtonWidgetSettingsComponent + PowerButtonWidgetSettingsComponent, + SliderWidgetSettingsComponent ], imports: [ CommonModule, @@ -562,7 +566,8 @@ import { SingleSwitchWidgetSettingsComponent, ActionButtonWidgetSettingsComponent, CommandButtonWidgetSettingsComponent, - PowerButtonWidgetSettingsComponent + PowerButtonWidgetSettingsComponent, + SliderWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -651,5 +656,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type { if (this.snackBarRef) { this.snackBarRef.dismiss(); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 19981797f8..296c56c020 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6342,6 +6342,31 @@ "off-label": "Off label", "switch": "Switch" }, + "slider": { + "behavior": "Behavior", + "initial-value": "Initial value", + "initial-value-hint": "Action to get the initial value of the slider.", + "on-value-change": "On value change", + "on-value-change-hint": "Action triggered when the slider value is changed.", + "layout": "Layout", + "layout-default": "Default", + "layout-extended": "Extended", + "layout-simplified": "Simplified", + "auto-scale": "Auto scale", + "icon": "Icon", + "value": "Value", + "range": "Range", + "min": "min", + "max": "max", + "range-ticks": "Range ticks", + "tick-marks": "Tick marks", + "colors": "Colors", + "main": "Main", + "background": "Background", + "left-icon": "Left icon", + "right-icon": "Right icon", + "slider": "Slider" + }, "value-card": { "layout": "Layout", "layout-square": "Square", diff --git a/ui-ngx/src/assets/widget/slider/default-layout.svg b/ui-ngx/src/assets/widget/slider/default-layout.svg new file mode 100644 index 0000000000..0b6d1bce33 --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/default-layout.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/slider/extended-layout.svg b/ui-ngx/src/assets/widget/slider/extended-layout.svg new file mode 100644 index 0000000000..923ee8af3d --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/extended-layout.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/widget/slider/simplified-layout.svg b/ui-ngx/src/assets/widget/slider/simplified-layout.svg new file mode 100644 index 0000000000..d2e3936db0 --- /dev/null +++ b/ui-ngx/src/assets/widget/slider/simplified-layout.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 094cff6174039febbf3fc3a4b64bc99085f79942 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 14 Feb 2024 14:01:20 +0200 Subject: [PATCH 123/137] Tell failure if originator is not a device --- .../thingsboard/rule/engine/action/TbDeviceStateNode.java | 7 +++++-- .../rule/engine/action/TbDeviceStateNodeTest.java | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index 9d6901ea01..b0f426d905 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -98,8 +98,11 @@ public class TbDeviceStateNode implements TbNode { return; } - if (!EntityType.DEVICE.equals(msg.getOriginator().getEntityType())) { - ctx.tellSuccess(msg); + EntityType originatorEntityType = msg.getOriginator().getEntityType(); + if (!EntityType.DEVICE.equals(originatorEntityType)) { + ctx.tellFailure(msg, new IllegalArgumentException( + "Unsupported originator entity type: [" + originatorEntityType + "]. Only DEVICE entity type is supported." + )); return; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index d66babea9a..19cb3c55bd 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -263,7 +263,12 @@ public class TbDeviceStateNodeTest { node.onMsg(ctxMock, msg); // THEN - then(ctxMock).should().tellSuccess(msg); + var exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + then(ctxMock).should().tellFailure(eq(msg), exceptionCaptor.capture()); + assertThat(exceptionCaptor.getValue()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported originator entity type: [" + unsupportedType + "]. Only DEVICE entity type is supported."); + then(ctxMock).shouldHaveNoMoreInteractions(); } From 541f6a3314099fd8614d06a97a0418ec939dbb77 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 14 Feb 2024 15:50:31 +0200 Subject: [PATCH 124/137] lwm2m/coap: client queue Mode --- .../coap/client/DefaultCoapClientContext.java | 15 ++++++++------- .../server/client/LwM2mClientContextImpl.java | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 735ec1d524..eaf105ee48 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -220,7 +220,7 @@ public class DefaultCoapClientContext implements CoapClientContext { private void onUplink(TbCoapClientState client, boolean notifyOtherServers, long uplinkTs) { PowerMode powerMode = client.getPowerMode(); PowerSavingConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); if (clientProfile.isPresent()) { profileSettings = clientProfile.get().getClientSettings(); @@ -726,7 +726,7 @@ public class DefaultCoapClientContext implements CoapClientContext { private boolean isDownlinkAllowed(TbCoapClientState client) { PowerMode powerMode = client.getPowerMode(); PowerSavingConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); if (clientProfile.isPresent()) { profileSettings = clientProfile.get().getClientSettings(); @@ -775,11 +775,12 @@ public class DefaultCoapClientContext implements CoapClientContext { private PowerMode getPowerMode(TbCoapClientState client) { PowerMode powerMode = client.getPowerMode(); if (powerMode == null) { - Optional deviceProfile = getProfile(client.getProfileId()); - if (deviceProfile.isPresent()) { - powerMode = deviceProfile.get().getClientSettings().getPowerMode(); - } else { - powerMode = PowerMode.PSM; + powerMode = PowerMode.PSM; + if (client.getProfileId() != null) { + Optional deviceProfile = getProfile(client.getProfileId()); + if (deviceProfile.isPresent()) { + powerMode = deviceProfile.get().getClientSettings().getPowerMode(); + } } } return powerMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 03449aaaef..18e27ba568 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -411,7 +411,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { public boolean isDownlinkAllowed(LwM2mClient client) { PowerMode powerMode = client.getPowerMode(); OtherConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); profileSettings = clientProfile.getClientLwM2mSettings(); powerMode = profileSettings.getPowerMode(); @@ -419,7 +419,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { powerMode = PowerMode.DRX; } } - if (PowerMode.DRX.equals(powerMode) || otaUpdateService.isOtaDownloading(client)) { + if (powerMode == null || PowerMode.DRX.equals(powerMode) || otaUpdateService.isOtaDownloading(client)) { return true; } client.lock(); @@ -460,7 +460,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { public void onUplink(LwM2mClient client) { PowerMode powerMode = client.getPowerMode(); OtherConfiguration profileSettings = null; - if (powerMode == null) { + if (powerMode == null && client.getProfileId() != null) { var clientProfile = getProfile(client.getProfileId()); profileSettings = clientProfile.getClientLwM2mSettings(); powerMode = profileSettings.getPowerMode(); @@ -468,7 +468,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { powerMode = PowerMode.DRX; } } - if (PowerMode.DRX.equals(powerMode)) { + if (powerMode == null || PowerMode.DRX.equals(powerMode)) { client.updateLastUplinkTime(); return; } From a65396c40c82654ab299c6baf61b1788de030b03 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Feb 2024 16:34:56 +0200 Subject: [PATCH 125/137] UI: Minor improvements --- .../src/main/data/json/system/widget_types/progress_bar.json | 3 +-- .../json/system/widget_types/simple_value_and_chart_card.json | 3 +-- .../basic/cards/progress-bar-basic-config.component.html | 4 ++-- .../cards/progress-bar-widget-settings.component.html | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/progress_bar.json b/application/src/main/data/json/system/widget_types/progress_bar.json index 1a92b6d470..6e60c20942 100644 --- a/application/src/main/data/json/system/widget_types/progress_bar.json +++ b/application/src/main/data/json/system/widget_types/progress_bar.json @@ -2,7 +2,7 @@ "fqn": "progress_bar", "name": "Progress bar", "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAeFBMVEXg4ODf39/g4ODg4OAAAAD////g4OD19fUhISE/Ut3j4+OsrKzHx8eQkJB0dHQ8PDzx8fEvLy+6urrV1dWCgoJYWFienp5KSkrv7++GhoZmZmaXl5f39/epqanLy8vCwsL29vZtfORiceJ+fn7m5ubU1NTn6fve4fIbcz38AAAABXRSTlPvIL+vAC9A4IoAAASZSURBVHja7d0NT9swEIDhMji4nb/jxEk/NmCM7f//w/niQLtJyxamtdfiF6EmDkh+1Di0IojVzdWH1e25d311s7paDXD2+cy49nABDfm0gotodSmQ2woRVoVIq0KkVSHSqhBpVYi0KkRaFSKtGUijctrDv6XQwEzHgDjkXLoAiKaoAurzhxAAGGxBm2gMgE+25SHeUNE0QKbRNo/otlWQo2QNQa6xrdpD1DSqW6vyBhmtrD86RKHNGIcOyOUHZJDDEEIe1xgQdT487gGF0CNmgUXnsH+BhPzhCKDFfNh50OgQ6dgQv0HFEB2hw8QYDQo3HlqGZFf0066GDiNQFmvcMEZNEMdH2zza8WhivopwVEjX9QF7yBCVSbwFCc04YdBoy4xhkw3QYAsWjQciiNhH8OT3a8RnXo4ooRm/M3dMSN91m+QnCM+6zN9h2ShTyq4xy08X9mpcVxgswR4yfktymDsJhICbIPEA4n+GJJXTfNS48atIWQzxABKQl0YEJQDiQ/Dl1LeoDyHtONWYj41TDtDYvJMwTZA2w7ADwwNJAAQS9sqgY4NLyb1CKGCrGNeh0QY7Pq6bHvUEQaMcKh5VJkiAgAmIHfGGw7B5hQD1iMHkR4vlC5TLA+l1jeS9dlo5LdojQeYj/7rBp8xrnvz+kSOCg349WjolpES9itphBEm9BeI3OJ46omLI8qhppP2e6z28sTqvKkRaFSKtCpFWhUirQqRVIdKqEGlViLQqRFoVIq0KkVaFSKtCpFUh0qoQaVWItCpEWhUirQqR1jzER609HEbSbhT4G4g3AXOWYKrpAnYgszlIjyVXJNTl7XOEJMQ+ArXT5L3LpBQJZDYD6RGJAQED5DKoBbnN3uTfvIAAgBB7EFyBzBbQlXsTNV/EQGh/hPgWUZW7e1PglR+hJOw6PA/RfCs2Gsg5nApFYvtO1L2A8xC1n3ne6DWQRewKEfQGSM7FeB5CSrUOUY0QN13D0DPEQuzANiAlhsxHAYNnSA9ci6gZ5Jq2VRbEVCCzWcRYrl2cKRAgk6gT9NPx9xBvbTqYeofBTyz9IlTaiDm3Zp6RckoxAePIScxzGKBEPTnVK5DRDMS+vtZyLMguBbTZv1BxpAw0UpbJDMQ7LAWCXIMl52EsGVAtKCmvv2YgQNYxY0PTbs97xh9SN07KemfITFHHw4nzntA3jO/kPfsZVSHSqhBpVYi0KkRaFSKtCpHWO4B8e/y4rC/f7/5/D8shXz4u7fHuCO0WQz4u7+4YLYY8LnZ8uTtCu+VrZOm59XiMNbJ7x1etM6tCpFUh0qoQac1A7s325L+P2t1/AoBhu90O04SWQ4b1ANsTP10Pa7XOD5/u4fMWbte74WlYDnlOAM3Jn5KBIesBHp7gXmXS5+WQz5/EQOz42TQVIgWyHgDyqfVGyPD0AOkZThxD+JrznBf7NpO+LodAszZbOHEF8nW9ZYEyZvnll9vtQErDNKH3/ZP9vKoQaVWItCpEWhUirQqRVoVIq0KkVSHSqhBpVYi0LgiygovodvXhAv71NIC/Xl2txPzpxNsbMuPm6vr23Ft9uLr5AdpbpSAPQyy7AAAAAElFTkSuQmCC", + "image": "tb-image:cHJvZ3Jlc3NfYmFyX3N5c3RlbV93aWRnZXRfaW1hZ2UucG5n:IlByb2dyZXNzIGJhciIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF80Njg1XzQwOTg5KSI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF80Njg1XzQwOTg5KSI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTYwIiByeD0iNCIgZmlsbD0id2hpdGUiIHNoYXBlLXJlbmRlcmluZz0iY3Jpc3BFZGdlcyIvPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMV9iXzQ2ODVfNDA5ODkpIj4KPHJlY3QgeD0iMTIiIHk9IjEyIiB3aWR0aD0iMTc2IiBoZWlnaHQ9IjEzNiIgcng9IjQiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNzYiLz4KPC9nPgo8cGF0aCBkPSJNMTYuNDQ5NyAxOS41NTMySDE0LjA0MzlWMTguMjlIMTYuNDQ5N0MxNi44Njg3IDE4LjI5IDE3LjIwNzIgMTguMjIyMyAxNy40NjUzIDE4LjA4NjlDMTcuNzIzNSAxNy45NTE1IDE3LjkxMTggMTcuNzY1MyAxOC4wMzAzIDE3LjUyODNDMTguMTUzIDE3LjI4NzEgMTguMjE0NCAxNy4wMTIgMTguMjE0NCAxNi43MDMxQzE4LjIxNDQgMTYuNDExMSAxOC4xNTMgMTYuMTM4MiAxOC4wMzAzIDE1Ljg4NDNDMTcuOTExOCAxNS42MjYxIDE3LjcyMzUgMTUuNDE4OCAxNy40NjUzIDE1LjI2MjJDMTcuMjA3MiAxNS4xMDU2IDE2Ljg2ODcgMTUuMDI3MyAxNi40NDk3IDE1LjAyNzNIMTQuNTMyN1YyM0gxMi45Mzk1VjEzLjc1NzhIMTYuNDQ5N0MxNy4xNjQ5IDEzLjc1NzggMTcuNzcyMSAxMy44ODQ4IDE4LjI3MTUgMTQuMTM4N0MxOC43NzUxIDE0LjM4ODMgMTkuMTU4IDE0LjczNTQgMTkuNDIwNCAxNS4xNzk3QzE5LjY4MjggMTUuNjE5OCAxOS44MTQgMTYuMTIzNCAxOS44MTQgMTYuNjkwNEMxOS44MTQgMTcuMjg3MSAxOS42ODI4IDE3Ljc5OTIgMTkuNDIwNCAxOC4yMjY2QzE5LjE1OCAxOC42NTQgMTguNzc1MSAxOC45ODE5IDE4LjI3MTUgMTkuMjEwNEMxNy43NzIxIDE5LjQzOSAxNy4xNjQ5IDE5LjU1MzIgMTYuNDQ5NyAxOS41NTMyWk0yMi44NzYgMTcuNDM5NVYyM0gyMS4zNDYyVjE2LjEzMThIMjIuODA2MkwyMi44NzYgMTcuNDM5NVpNMjQuOTc3MSAxNi4wODc0TDI0Ljk2NDQgMTcuNTA5M0MyNC44NzEzIDE3LjQ5MjQgMjQuNzY5NyAxNy40Nzk3IDI0LjY1OTcgMTcuNDcxMkMyNC41NTM5IDE3LjQ2MjcgMjQuNDQ4MSAxNy40NTg1IDI0LjM0MjMgMTcuNDU4NUMyNC4wNzk5IDE3LjQ1ODUgMjMuODQ5MyAxNy40OTY2IDIzLjY1MDQgMTcuNTcyOEMyMy40NTE1IDE3LjY0NDcgMjMuMjg0MyAxNy43NTA1IDIzLjE0ODkgMTcuODkwMUMyMy4wMTc3IDE4LjAyNTYgMjIuOTE2MiAxOC4xOTA2IDIyLjg0NDIgMTguMzg1M0MyMi43NzIzIDE4LjU3OTkgMjIuNzMgMTguNzk3OSAyMi43MTczIDE5LjAzOTFMMjIuMzY4MiAxOS4wNjQ1QzIyLjM2ODIgMTguNjMyOCAyMi40MTA1IDE4LjIzMjkgMjIuNDk1MSAxNy44NjQ3QzIyLjU3OTggMTcuNDk2NiAyMi43MDY3IDE3LjE3MjkgMjIuODc2IDE2Ljg5MzZDMjMuMDQ5NSAxNi42MTQzIDIzLjI2NTMgMTYuMzk2MyAyMy41MjM0IDE2LjIzOTdDMjMuNzg1OCAxNi4wODMyIDI0LjA4ODQgMTYuMDA0OSAyNC40MzEyIDE2LjAwNDlDMjQuNTI0MyAxNi4wMDQ5IDI0LjYyMzcgMTYuMDEzMyAyNC43Mjk1IDE2LjAzMDNDMjQuODM5NSAxNi4wNDcyIDI0LjkyMiAxNi4wNjYyIDI0Ljk3NzEgMTYuMDg3NFpNMjUuNzI4NSAxOS42NDIxVjE5LjQ5NjFDMjUuNzI4NSAxOS4wMDEgMjUuODAwNSAxOC41NDE4IDI1Ljk0NDMgMTguMTE4N0MyNi4wODgyIDE3LjY5MTIgMjYuMjk1NiAxNy4zMjEgMjYuNTY2NCAxNy4wMDc4QzI2Ljg0MTUgMTYuNjkwNCAyNy4xNzU4IDE2LjQ0NSAyNy41NjkzIDE2LjI3MTVDMjcuOTY3MSAxNi4wOTM4IDI4LjQxNTcgMTYuMDA0OSAyOC45MTUgMTYuMDA0OUMyOS40MTg2IDE2LjAwNDkgMjkuODY3MiAxNi4wOTM4IDMwLjI2MDcgMTYuMjcxNUMzMC42NTg1IDE2LjQ0NSAzMC45OTUgMTYuNjkwNCAzMS4yNyAxNy4wMDc4QzMxLjU0NTEgMTcuMzIxIDMxLjc1NDYgMTcuNjkxMiAzMS44OTg0IDE4LjExODdDMzIuMDQyMyAxOC41NDE4IDMyLjExNDMgMTkuMDAxIDMyLjExNDMgMTkuNDk2MVYxOS42NDIxQzMyLjExNDMgMjAuMTM3MiAzMi4wNDIzIDIwLjU5NjQgMzEuODk4NCAyMS4wMTk1QzMxLjc1NDYgMjEuNDQyNyAzMS41NDUxIDIxLjgxMyAzMS4yNyAyMi4xMzA0QzMwLjk5NSAyMi40NDM1IDMwLjY2MDYgMjIuNjg5IDMwLjI2NzEgMjIuODY2N0MyOS44NzM1IDIzLjA0MDIgMjkuNDI3MSAyMy4xMjcgMjguOTI3NyAyMy4xMjdDMjguNDI0MiAyMy4xMjcgMjcuOTczNSAyMy4wNDAyIDI3LjU3NTcgMjIuODY2N0MyNy4xODIxIDIyLjY4OSAyNi44NDc4IDIyLjQ0MzUgMjYuNTcyOCAyMi4xMzA0QzI2LjI5NzcgMjEuODEzIDI2LjA4ODIgMjEuNDQyNyAyNS45NDQzIDIxLjAxOTVDMjUuODAwNSAyMC41OTY0IDI1LjcyODUgMjAuMTM3MiAyNS43Mjg1IDE5LjY0MjFaTTI3LjI1ODMgMTkuNDk2MVYxOS42NDIxQzI3LjI1ODMgMTkuOTUxIDI3LjI5IDIwLjI0MyAyNy4zNTM1IDIwLjUxODFDMjcuNDE3IDIwLjc5MzEgMjcuNTE2NCAyMS4wMzQzIDI3LjY1MTkgMjEuMjQxN0MyNy43ODczIDIxLjQ0OTEgMjcuOTYwOCAyMS42MTIgMjguMTcyNCAyMS43MzA1QzI4LjM4NCAyMS44NDkgMjguNjM1NyAyMS45MDgyIDI4LjkyNzcgMjEuOTA4MkMyOS4yMTEzIDIxLjkwODIgMjkuNDU2NyAyMS44NDkgMjkuNjY0MSAyMS43MzA1QzI5Ljg3NTcgMjEuNjEyIDMwLjA0OTIgMjEuNDQ5MSAzMC4xODQ2IDIxLjI0MTdDMzAuMzIgMjEuMDM0MyAzMC40MTk0IDIwLjc5MzEgMzAuNDgyOSAyMC41MTgxQzMwLjU1MDYgMjAuMjQzIDMwLjU4NDUgMTkuOTUxIDMwLjU4NDUgMTkuNjQyMVYxOS40OTYxQzMwLjU4NDUgMTkuMTkxNCAzMC41NTA2IDE4LjkwMzYgMzAuNDgyOSAxOC42MzI4QzMwLjQxOTQgMTguMzU3NyAzMC4zMTc5IDE4LjExNDQgMzAuMTc4MiAxNy45MDI4QzMwLjA0MjggMTcuNjkxMiAyOS44NjkzIDE3LjUyNjIgMjkuNjU3NyAxNy40MDc3QzI5LjQ1MDQgMTcuMjg1IDI5LjIwMjggMTcuMjIzNiAyOC45MTUgMTcuMjIzNkMyOC42MjczIDE3LjIyMzYgMjguMzc3NiAxNy4yODUgMjguMTY2IDE3LjQwNzdDMjcuOTU4NyAxNy41MjYyIDI3Ljc4NzMgMTcuNjkxMiAyNy42NTE5IDE3LjkwMjhDMjcuNTE2NCAxOC4xMTQ0IDI3LjQxNyAxOC4zNTc3IDI3LjM1MzUgMTguNjMyOEMyNy4yOSAxOC45MDM2IDI3LjI1ODMgMTkuMTkxNCAyNy4yNTgzIDE5LjQ5NjFaTTM4LjA0NTQgMTYuMTMxOEgzOS40MzU1VjIyLjgwOTZDMzkuNDM1NSAyMy40Mjc0IDM5LjMwNDQgMjMuOTUyMSAzOS4wNDIgMjQuMzgzOEMzOC43Nzk2IDI0LjgxNTQgMzguNDEzNiAyNS4xNDM0IDM3Ljk0MzggMjUuMzY3N0MzNy40NzQxIDI1LjU5NjIgMzYuOTMwMyAyNS43MTA0IDM2LjMxMjUgMjUuNzEwNEMzNi4wNTAxIDI1LjcxMDQgMzUuNzU4MSAyNS42NzI0IDM1LjQzNjUgMjUuNTk2MkMzNS4xMTkxIDI1LjUyIDM0LjgxMDIgMjUuMzk3MyAzNC41MDk4IDI1LjIyOEMzNC4yMTM1IDI1LjA2MyAzMy45NjYgMjQuODQ1MSAzMy43NjcxIDI0LjU3NDJMMzQuNDg0NCAyMy42NzI5QzM0LjcyOTggMjMuOTY0OCAzNS4wMDA3IDI0LjE3ODUgMzUuMjk2OSAyNC4zMTRDMzUuNTkzMSAyNC40NDk0IDM1LjkwNDEgMjQuNTE3MSAzNi4yMyAyNC41MTcxQzM2LjU4MTIgMjQuNTE3MSAzNi44Nzk2IDI0LjQ1MTUgMzcuMTI1IDI0LjMyMDNDMzcuMzc0NyAyNC4xOTM0IDM3LjU2NzIgMjQuMDA1IDM3LjcwMjYgMjMuNzU1NEMzNy44MzgxIDIzLjUwNTcgMzcuOTA1OCAyMy4yMDEgMzcuOTA1OCAyMi44NDEzVjE3LjY4N0wzOC4wNDU0IDE2LjEzMThaTTMzLjM3OTkgMTkuNjQyMVYxOS41MDg4QzMzLjM3OTkgMTguOTg4MyAzMy40NDM0IDE4LjUxNDMgMzMuNTcwMyAxOC4wODY5QzMzLjY5NzMgMTcuNjU1MyAzMy44NzkyIDE3LjI4NSAzNC4xMTYyIDE2Ljk3NjFDMzQuMzUzMiAxNi42NjI5IDM0LjY0MSAxNi40MjM4IDM0Ljk3OTUgMTYuMjU4OEMzNS4zMTggMTYuMDg5NSAzNS43MDEgMTYuMDA0OSAzNi4xMjg0IDE2LjAwNDlDMzYuNTcyOCAxNi4wMDQ5IDM2Ljk1MTUgMTYuMDg1MyAzNy4yNjQ2IDE2LjI0NjFDMzcuNTgyIDE2LjQwNjkgMzcuODQ2NSAxNi42Mzc1IDM4LjA1ODEgMTYuOTM4QzM4LjI2OTcgMTcuMjM0MiAzOC40MzQ3IDE3LjU4OTcgMzguNTUzMiAxOC4wMDQ0QzM4LjY3NTkgMTguNDE0OSAzOC43NjY5IDE4Ljg3MTkgMzguODI2MiAxOS4zNzU1VjE5LjgwMDhDMzguNzcxMiAyMC4yOTE3IDM4LjY3ODEgMjAuNzQwMiAzOC41NDY5IDIxLjE0NjVDMzguNDE1NyAyMS41NTI3IDM4LjI0MjIgMjEuOTA0IDM4LjAyNjQgMjIuMjAwMkMzNy44MTA1IDIyLjQ5NjQgMzcuNTQzOSAyMi43MjQ5IDM3LjIyNjYgMjIuODg1N0MzNi45MTM0IDIzLjA0NjUgMzYuNTQzMSAyMy4xMjcgMzYuMTE1NyAyMy4xMjdDMzUuNjk2OCAyMy4xMjcgMzUuMzE4IDIzLjA0MDIgMzQuOTc5NSAyMi44NjY3QzM0LjY0NTIgMjIuNjkzMiAzNC4zNTc0IDIyLjQ0OTkgMzQuMTE2MiAyMi4xMzY3QzMzLjg3OTIgMjEuODIzNiAzMy42OTczIDIxLjQ1NTQgMzMuNTcwMyAyMS4wMzIyQzMzLjQ0MzQgMjAuNjA0OCAzMy4zNzk5IDIwLjE0MTQgMzMuMzc5OSAxOS42NDIxWk0zNC45MDk3IDE5LjUwODhWMTkuNjQyMUMzNC45MDk3IDE5Ljk1NTIgMzQuOTM5MyAyMC4yNDcyIDM0Ljk5ODUgMjAuNTE4MUMzNS4wNjIgMjAuNzg4OSAzNS4xNTcyIDIxLjAyOCAzNS4yODQyIDIxLjIzNTRDMzUuNDE1NCAyMS40Mzg1IDM1LjU4MDQgMjEuNTk5MyAzNS43NzkzIDIxLjcxNzhDMzUuOTgyNCAyMS44MzIgMzYuMjIxNSAyMS44ODkyIDM2LjQ5NjYgMjEuODg5MkMzNi44NTYzIDIxLjg4OTIgMzcuMTUwNCAyMS44MTMgMzcuMzc4OSAyMS42NjA2QzM3LjYxMTcgMjEuNTA4MyAzNy43ODk0IDIxLjMwMzEgMzcuOTEyMSAyMS4wNDQ5QzM4LjAzOTEgMjAuNzgyNiAzOC4xMjc5IDIwLjQ5MDYgMzguMTc4NyAyMC4xNjg5VjE5LjAyQzM4LjE1MzMgMTguNzcwMyAzOC4xMDA0IDE4LjUzNzYgMzguMDIgMTguMzIxOEMzNy45NDM4IDE4LjEwNiAzNy44NDAyIDE3LjkxNzYgMzcuNzA5IDE3Ljc1NjhDMzcuNTc3OCAxNy41OTE4IDM3LjQxMjggMTcuNDY0OCAzNy4yMTM5IDE3LjM3NkMzNy4wMTUgMTcuMjgyOSAzNi43ODAxIDE3LjIzNjMgMzYuNTA5MyAxNy4yMzYzQzM2LjIzNDIgMTcuMjM2MyAzNS45OTUxIDE3LjI5NTYgMzUuNzkyIDE3LjQxNDFDMzUuNTg4OSAxNy41MzI2IDM1LjQyMTcgMTcuNjk1NSAzNS4yOTA1IDE3LjkwMjhDMzUuMTYzNiAxOC4xMTAyIDM1LjA2ODQgMTguMzUxNCAzNS4wMDQ5IDE4LjYyNjVDMzQuOTQxNCAxOC45MDE1IDM0LjkwOTcgMTkuMTk1NiAzNC45MDk3IDE5LjUwODhaTTQyLjgwODYgMTcuNDM5NVYyM0g0MS4yNzg4VjE2LjEzMThINDIuNzM4OEw0Mi44MDg2IDE3LjQzOTVaTTQ0LjkwOTcgMTYuMDg3NEw0NC44OTcgMTcuNTA5M0M0NC44MDM5IDE3LjQ5MjQgNDQuNzAyMyAxNy40Nzk3IDQ0LjU5MjMgMTcuNDcxMkM0NC40ODY1IDE3LjQ2MjcgNDQuMzgwNyAxNy40NTg1IDQ0LjI3NDkgMTcuNDU4NUM0NC4wMTI1IDE3LjQ1ODUgNDMuNzgxOSAxNy40OTY2IDQzLjU4MyAxNy41NzI4QzQzLjM4NDEgMTcuNjQ0NyA0My4yMTcgMTcuNzUwNSA0My4wODE1IDE3Ljg5MDFDNDIuOTUwNCAxOC4wMjU2IDQyLjg0ODggMTguMTkwNiA0Mi43NzY5IDE4LjM4NTNDNDIuNzA0OSAxOC41Nzk5IDQyLjY2MjYgMTguNzk3OSA0Mi42NDk5IDE5LjAzOTFMNDIuMzAwOCAxOS4wNjQ1QzQyLjMwMDggMTguNjMyOCA0Mi4zNDMxIDE4LjIzMjkgNDIuNDI3NyAxNy44NjQ3QzQyLjUxMjQgMTcuNDk2NiA0Mi42MzkzIDE3LjE3MjkgNDIuODA4NiAxNi44OTM2QzQyLjk4MjEgMTYuNjE0MyA0My4xOTc5IDE2LjM5NjMgNDMuNDU2MSAxNi4yMzk3QzQzLjcxODQgMTYuMDgzMiA0NC4wMjEgMTYuMDA0OSA0NC4zNjM4IDE2LjAwNDlDNDQuNDU2OSAxNi4wMDQ5IDQ0LjU1NjMgMTYuMDEzMyA0NC42NjIxIDE2LjAzMDNDNDQuNzcyMSAxNi4wNDcyIDQ0Ljg1NDcgMTYuMDY2MiA0NC45MDk3IDE2LjA4NzRaTTQ5LjAxOSAyMy4xMjdDNDguNTExMiAyMy4xMjcgNDguMDUyMSAyMy4wNDQ0IDQ3LjY0MTYgMjIuODc5NEM0Ny4yMzU0IDIyLjcxMDEgNDYuODg4MyAyMi40NzUzIDQ2LjYwMDYgMjIuMTc0OEM0Ni4zMTcxIDIxLjg3NDMgNDYuMDk5MSAyMS41MjEgNDUuOTQ2OCAyMS4xMTQ3QzQ1Ljc5NDQgMjAuNzA4NSA0NS43MTgzIDIwLjI3MDUgNDUuNzE4MyAxOS44MDA4VjE5LjU0NjlDNDUuNzE4MyAxOS4wMDk0IDQ1Ljc5NjUgMTguNTIyOCA0NS45NTMxIDE4LjA4NjlDNDYuMTA5NyAxNy42NTEgNDYuMzI3NiAxNy4yNzg2IDQ2LjYwNjkgMTYuOTY5N0M0Ni44ODYyIDE2LjY1NjYgNDcuMjE2MyAxNi40MTc1IDQ3LjU5NzIgMTYuMjUyNEM0Ny45NzggMTYuMDg3NCA0OC4zOTA2IDE2LjAwNDkgNDguODM1IDE2LjAwNDlDNDkuMzI1OCAxNi4wMDQ5IDQ5Ljc1NTQgMTYuMDg3NCA1MC4xMjM1IDE2LjI1MjRDNTAuNDkxNyAxNi40MTc1IDUwLjc5NjQgMTYuNjUwMiA1MS4wMzc2IDE2Ljk1MDdDNTEuMjgzIDE3LjI0NjkgNTEuNDY1IDE3LjYwMDMgNTEuNTgzNSAxOC4wMTA3QzUxLjcwNjIgMTguNDIxMiA1MS43Njc2IDE4Ljg3NCA1MS43Njc2IDE5LjM2OTFWMjAuMDIyOUg0Ni40NjA5VjE4LjkyNDhINTAuMjU2OFYxOC44MDQyQzUwLjI0ODQgMTguNTI5MSA1MC4xOTM0IDE4LjI3MSA1MC4wOTE4IDE4LjAyOThDNDkuOTk0NSAxNy43ODg2IDQ5Ljg0NDIgMTcuNTkzOSA0OS42NDExIDE3LjQ0NThDNDkuNDM4IDE3LjI5NzcgNDkuMTY3MiAxNy4yMjM2IDQ4LjgyODYgMTcuMjIzNkM0OC41NzQ3IDE3LjIyMzYgNDguMzQ4MyAxNy4yNzg2IDQ4LjE0OTQgMTcuMzg4N0M0Ny45NTQ4IDE3LjQ5NDUgNDcuNzkxOCAxNy42NDg5IDQ3LjY2MDYgMTcuODUyMUM0Ny41Mjk1IDE4LjA1NTIgNDcuNDI3OSAxOC4zMDA2IDQ3LjM1NiAxOC41ODg0QzQ3LjI4ODIgMTguODcxOSA0Ny4yNTQ0IDE5LjE5MTQgNDcuMjU0NCAxOS41NDY5VjE5LjgwMDhDNDcuMjU0NCAyMC4xMDEyIDQ3LjI5NDYgMjAuMzgwNSA0Ny4zNzUgMjAuNjM4N0M0Ny40NTk2IDIwLjg5MjYgNDcuNTgyNCAyMS4xMTQ3IDQ3Ljc0MzIgMjEuMzA1MkM0Ny45MDQgMjEuNDk1NiA0OC4wOTg2IDIxLjY0NTggNDguMzI3MSAyMS43NTU5QzQ4LjU1NTcgMjEuODYxNyA0OC44MTU5IDIxLjkxNDYgNDkuMTA3OSAyMS45MTQ2QzQ5LjQ3NjEgMjEuOTE0NiA0OS44MDQgMjEuODQwNSA1MC4wOTE4IDIxLjY5MjRDNTAuMzc5NiAyMS41NDQzIDUwLjYyOTIgMjEuMzM0OCA1MC44NDA4IDIxLjA2NEw1MS42NDcgMjEuODQ0N0M1MS40OTg5IDIyLjA2MDUgNTEuMzA2MyAyMi4yNjc5IDUxLjA2OTMgMjIuNDY2OEM1MC44MzI0IDIyLjY2MTUgNTAuNTQyNSAyMi44MjAxIDUwLjE5OTcgMjIuOTQyOUM0OS44NjEyIDIzLjA2NTYgNDkuNDY3NiAyMy4xMjcgNDkuMDE5IDIzLjEyN1pNNTcuMDY0IDIxLjE0MDFDNTcuMDY0IDIwLjk4NzggNTcuMDI1OSAyMC44NTAzIDU2Ljk0OTcgMjAuNzI3NUM1Ni44NzM1IDIwLjYwMDYgNTYuNzI3NSAyMC40ODYzIDU2LjUxMTcgMjAuMzg0OEM1Ni4zMDAxIDIwLjI4MzIgNTUuOTg3IDIwLjE5MDEgNTUuNTcyMyAyMC4xMDU1QzU1LjIwODMgMjAuMDI1MSA1NC44NzQgMTkuOTI5OSA1NC41NjkzIDE5LjgxOThDNTQuMjY4OSAxOS43MDU2IDU0LjAxMDcgMTkuNTY4IDUzLjc5NDkgMTkuNDA3MkM1My41NzkxIDE5LjI0NjQgNTMuNDExOSAxOS4wNTYgNTMuMjkzNSAxOC44MzU5QzUzLjE3NSAxOC42MTU5IDUzLjExNTcgMTguMzYyIDUzLjExNTcgMTguMDc0MkM1My4xMTU3IDE3Ljc5NDkgNTMuMTc3MSAxNy41MzA0IDUzLjI5OTggMTcuMjgwOEM1My40MjI1IDE3LjAzMTEgNTMuNTk4MSAxNi44MTEgNTMuODI2NyAxNi42MjA2QzU0LjA1NTIgMTYuNDMwMiA1NC4zMzI0IDE2LjI3OTkgNTQuNjU4MiAxNi4xNjk5QzU0Ljk4ODMgMTYuMDU5OSA1NS4zNTY0IDE2LjAwNDkgNTUuNzYyNyAxNi4wMDQ5QzU2LjMzODIgMTYuMDA0OSA1Ni44MzEyIDE2LjEwMjIgNTcuMjQxNyAxNi4yOTY5QzU3LjY1NjQgMTYuNDg3MyA1Ny45NzM4IDE2Ljc0NzYgNTguMTkzOCAxNy4wNzc2QzU4LjQxMzkgMTcuNDAzNSA1OC41MjM5IDE3Ljc3MTYgNTguNTIzOSAxOC4xODIxSDU2Ljk5NDFDNTYuOTk0MSAxOC4wMDAyIDU2Ljk0NzYgMTcuODMwOSA1Ni44NTQ1IDE3LjY3NDNDNTYuNzY1NiAxNy41MTM1IDU2LjYzMDIgMTcuMzg0NCA1Ni40NDgyIDE3LjI4NzFDNTYuMjY2MyAxNy4xODU1IDU2LjAzNzggMTcuMTM0OCA1NS43NjI3IDE3LjEzNDhDNTUuNTAwMyAxNy4xMzQ4IDU1LjI4MjQgMTcuMTc3MSA1NS4xMDg5IDE3LjI2MTdDNTQuOTM5NiAxNy4zNDIxIDU0LjgxMjcgMTcuNDQ3OSA1NC43MjggMTcuNTc5MUM1NC42NDc2IDE3LjcxMDMgNTQuNjA3NCAxNy44NTQyIDU0LjYwNzQgMTguMDEwN0M1NC42MDc0IDE4LjEyNSA1NC42Mjg2IDE4LjIyODcgNTQuNjcwOSAxOC4zMjE4QzU0LjcxNzQgMTguNDEwNiA1NC43OTM2IDE4LjQ5MzIgNTQuODk5NCAxOC41NjkzQzU1LjAwNTIgMTguNjQxMyA1NS4xNDkxIDE4LjcwOSA1NS4zMzExIDE4Ljc3MjVDNTUuNTE3MyAxOC44MzU5IDU1Ljc1IDE4Ljg5NzMgNTYuMDI5MyAxOC45NTY1QzU2LjU1NCAxOS4wNjY2IDU3LjAwNDcgMTkuMjA4MyA1Ny4zODEzIDE5LjM4MThDNTcuNzYyMiAxOS41NTExIDU4LjA1NDIgMTkuNzcxMiA1OC4yNTczIDIwLjA0MkM1OC40NjA0IDIwLjMwODYgNTguNTYyIDIwLjY0NzEgNTguNTYyIDIxLjA1NzZDNTguNTYyIDIxLjM2MjMgNTguNDk2NCAyMS42NDE2IDU4LjM2NTIgMjEuODk1NUM1OC4yMzgzIDIyLjE0NTIgNTguMDUyMSAyMi4zNjMxIDU3LjgwNjYgMjIuNTQ5M0M1Ny41NjEyIDIyLjczMTMgNTcuMjY3MSAyMi44NzMgNTYuOTI0MyAyMi45NzQ2QzU2LjU4NTggMjMuMDc2MiA1Ni4yMDQ5IDIzLjEyNyA1NS43ODE3IDIzLjEyN0M1NS4xNTk3IDIzLjEyNyA1NC42MzI4IDIzLjAxNjkgNTQuMjAxMiAyMi43OTY5QzUzLjc2OTUgMjIuNTcyNiA1My40NDE2IDIyLjI4NjkgNTMuMjE3MyAyMS45Mzk5QzUyLjk5NzIgMjEuNTg4NyA1Mi44ODcyIDIxLjIyNDggNTIuODg3MiAyMC44NDgxSDU0LjM2NjJDNTQuMzgzMSAyMS4xMzE3IDU0LjQ2MTQgMjEuMzU4MSA1NC42MDExIDIxLjUyNzNDNTQuNzQ1IDIxLjY5MjQgNTQuOTIyNyAyMS44MTMgNTUuMTM0MyAyMS44ODkyQzU1LjM1MDEgMjEuOTYxMSA1NS41NzIzIDIxLjk5NzEgNTUuODAwOCAyMS45OTcxQzU2LjA3NTggMjEuOTk3MSA1Ni4zMDY1IDIxLjk2MTEgNTYuNDkyNyAyMS44ODkyQzU2LjY3ODkgMjEuODEzIDU2LjgyMDYgMjEuNzExNCA1Ni45MTggMjEuNTg0NUM1Ny4wMTUzIDIxLjQ1MzMgNTcuMDY0IDIxLjMwNTIgNTcuMDY0IDIxLjE0MDFaTTY0LjAwNDQgMjEuMTQwMUM2NC4wMDQ0IDIwLjk4NzggNjMuOTY2MyAyMC44NTAzIDYzLjg5MDEgMjAuNzI3NUM2My44MTQgMjAuNjAwNiA2My42NjggMjAuNDg2MyA2My40NTIxIDIwLjM4NDhDNjMuMjQwNiAyMC4yODMyIDYyLjkyNzQgMjAuMTkwMSA2Mi41MTI3IDIwLjEwNTVDNjIuMTQ4OCAyMC4wMjUxIDYxLjgxNDUgMTkuOTI5OSA2MS41MDk4IDE5LjgxOThDNjEuMjA5MyAxOS43MDU2IDYwLjk1MTIgMTkuNTY4IDYwLjczNTQgMTkuNDA3MkM2MC41MTk1IDE5LjI0NjQgNjAuMzUyNCAxOS4wNTYgNjAuMjMzOSAxOC44MzU5QzYwLjExNTQgMTguNjE1OSA2MC4wNTYyIDE4LjM2MiA2MC4wNTYyIDE4LjA3NDJDNjAuMDU2MiAxNy43OTQ5IDYwLjExNzUgMTcuNTMwNCA2MC4yNDAyIDE3LjI4MDhDNjAuMzYzIDE3LjAzMTEgNjAuNTM4NiAxNi44MTEgNjAuNzY3MSAxNi42MjA2QzYwLjk5NTYgMTYuNDMwMiA2MS4yNzI4IDE2LjI3OTkgNjEuNTk4NiAxNi4xNjk5QzYxLjkyODcgMTYuMDU5OSA2Mi4yOTY5IDE2LjAwNDkgNjIuNzAzMSAxNi4wMDQ5QzYzLjI3ODYgMTYuMDA0OSA2My43NzE2IDE2LjEwMjIgNjQuMTgyMSAxNi4yOTY5QzY0LjU5NjggMTYuNDg3MyA2NC45MTQyIDE2Ljc0NzYgNjUuMTM0MyAxNy4wNzc2QzY1LjM1NDMgMTcuNDAzNSA2NS40NjQ0IDE3Ljc3MTYgNjUuNDY0NCAxOC4xODIxSDYzLjkzNDZDNjMuOTM0NiAxOC4wMDAyIDYzLjg4OCAxNy44MzA5IDYzLjc5NDkgMTcuNjc0M0M2My43MDYxIDE3LjUxMzUgNjMuNTcwNiAxNy4zODQ0IDYzLjM4ODcgMTcuMjg3MUM2My4yMDY3IDE3LjE4NTUgNjIuOTc4MiAxNy4xMzQ4IDYyLjcwMzEgMTcuMTM0OEM2Mi40NDA4IDE3LjEzNDggNjIuMjIyOCAxNy4xNzcxIDYyLjA0OTMgMTcuMjYxN0M2MS44OCAxNy4zNDIxIDYxLjc1MzEgMTcuNDQ3OSA2MS42Njg1IDE3LjU3OTFDNjEuNTg4MSAxNy43MTAzIDYxLjU0NzkgMTcuODU0MiA2MS41NDc5IDE4LjAxMDdDNjEuNTQ3OSAxOC4xMjUgNjEuNTY5IDE4LjIyODcgNjEuNjExMyAxOC4zMjE4QzYxLjY1NzkgMTguNDEwNiA2MS43MzQgMTguNDkzMiA2MS44Mzk4IDE4LjU2OTNDNjEuOTQ1NiAxOC42NDEzIDYyLjA4OTUgMTguNzA5IDYyLjI3MTUgMTguNzcyNUM2Mi40NTc3IDE4LjgzNTkgNjIuNjkwNCAxOC44OTczIDYyLjk2OTcgMTguOTU2NUM2My40OTQ1IDE5LjA2NjYgNjMuOTQ1MSAxOS4yMDgzIDY0LjMyMTggMTkuMzgxOEM2NC43MDI2IDE5LjU1MTEgNjQuOTk0NiAxOS43NzEyIDY1LjE5NzggMjAuMDQyQzY1LjQwMDkgMjAuMzA4NiA2NS41MDI0IDIwLjY0NzEgNjUuNTAyNCAyMS4wNTc2QzY1LjUwMjQgMjEuMzYyMyA2NS40MzY4IDIxLjY0MTYgNjUuMzA1NyAyMS44OTU1QzY1LjE3ODcgMjIuMTQ1MiA2NC45OTI1IDIyLjM2MzEgNjQuNzQ3MSAyMi41NDkzQzY0LjUwMTYgMjIuNzMxMyA2NC4yMDc1IDIyLjg3MyA2My44NjQ3IDIyLjk3NDZDNjMuNTI2MiAyMy4wNzYyIDYzLjE0NTMgMjMuMTI3IDYyLjcyMjIgMjMuMTI3QzYyLjEwMDEgMjMuMTI3IDYxLjU3MzIgMjMuMDE2OSA2MS4xNDE2IDIyLjc5NjlDNjAuNzEgMjIuNTcyNiA2MC4zODIgMjIuMjg2OSA2MC4xNTc3IDIxLjkzOTlDNTkuOTM3NyAyMS41ODg3IDU5LjgyNzYgMjEuMjI0OCA1OS44Mjc2IDIwLjg0ODFINjEuMzA2NkM2MS4zMjM2IDIxLjEzMTcgNjEuNDAxOSAyMS4zNTgxIDYxLjU0MTUgMjEuNTI3M0M2MS42ODU0IDIxLjY5MjQgNjEuODYzMSAyMS44MTMgNjIuMDc0NyAyMS44ODkyQzYyLjI5MDUgMjEuOTYxMSA2Mi41MTI3IDIxLjk5NzEgNjIuNzQxMiAyMS45OTcxQzYzLjAxNjMgMjEuOTk3MSA2My4yNDY5IDIxLjk2MTEgNjMuNDMzMSAyMS44ODkyQzYzLjYxOTMgMjEuODEzIDYzLjc2MTEgMjEuNzExNCA2My44NTg0IDIxLjU4NDVDNjMuOTU1NyAyMS40NTMzIDY0LjAwNDQgMjEuMzA1MiA2NC4wMDQ0IDIxLjE0MDFaTTcwLjU4NTQgMTMuMjVINzIuMTE1MlYyMS41MzM3TDcxLjk2OTIgMjNINzAuNTg1NFYxMy4yNVpNNzYuNTk2NyAxOS41MDI0VjE5LjYzNTdDNzYuNTk2NyAyMC4xNDM2IDc2LjUzOTYgMjAuNjExMiA3Ni40MjUzIDIxLjAzODZDNzYuMzE1MyAyMS40NjE4IDc2LjE0NiAyMS44Mjk5IDc1LjkxNzUgMjIuMTQzMUM3NS42OTMyIDIyLjQ1NjIgNzUuNDEzOSAyMi42OTk1IDc1LjA3OTYgMjIuODczQzc0Ljc0OTUgMjMuMDQyMyA3NC4zNjY1IDIzLjEyNyA3My45MzA3IDIzLjEyN0M3My41MDMzIDIzLjEyNyA3My4xMzA5IDIzLjA0NjUgNzIuODEzNSAyMi44ODU3QzcyLjQ5NjEgMjIuNzI0OSA3Mi4yMjk1IDIyLjQ5NjQgNzIuMDEzNyAyMi4yMDAyQzcxLjgwMjEgMjEuOTA0IDcxLjYzMDcgMjEuNTUwNiA3MS40OTk1IDIxLjE0MDFDNzEuMzY4MyAyMC43Mjk3IDcxLjI3NTIgMjAuMjc2OSA3MS4yMjAyIDE5Ljc4MTdWMTkuMzU2NEM3MS4yNzUyIDE4Ljg1NzEgNzEuMzY4MyAxOC40MDQzIDcxLjQ5OTUgMTcuOTk4QzcxLjYzMDcgMTcuNTg3NiA3MS44MDIxIDE3LjIzNDIgNzIuMDEzNyAxNi45MzhDNzIuMjI5NSAxNi42Mzc1IDcyLjQ5NCAxNi40MDY5IDcyLjgwNzEgMTYuMjQ2MUM3My4xMjQ1IDE2LjA4NTMgNzMuNDk0OCAxNi4wMDQ5IDczLjkxOCAxNi4wMDQ5Qzc0LjM1ODEgMTYuMDA0OSA3NC43NDUzIDE2LjA4OTUgNzUuMDc5NiAxNi4yNTg4Qzc1LjQxODEgMTYuNDI4MSA3NS42OTk1IDE2LjY2OTMgNzUuOTIzOCAxNi45ODI0Qzc2LjE0ODEgMTcuMjkxMyA3Ni4zMTUzIDE3LjY1OTUgNzYuNDI1MyAxOC4wODY5Qzc2LjUzOTYgMTguNTE0MyA3Ni41OTY3IDE4Ljk4NjIgNzYuNTk2NyAxOS41MDI0Wk03NS4wNjY5IDE5LjYzNTdWMTkuNTAyNEM3NS4wNjY5IDE5LjE5MzUgNzUuMDQxNSAxOC45MDM2IDc0Ljk5MDcgMTguNjMyOEM3NC45Mzk5IDE4LjM1NzcgNzQuODU1MyAxOC4xMTY1IDc0LjczNjggMTcuOTA5MkM3NC42MjI2IDE3LjcwMTggNzQuNDY2IDE3LjUzODkgNzQuMjY3MSAxNy40MjA0Qzc0LjA3MjQgMTcuMjk3NyA3My44MjkxIDE3LjIzNjMgNzMuNTM3MSAxNy4yMzYzQzczLjI2NjMgMTcuMjM2MyA3My4wMzM1IDE3LjI4MjkgNzIuODM4OSAxNy4zNzZDNzIuNjQ0MiAxNy40NjkxIDcyLjQ4MTMgMTcuNTk2IDcyLjM1MDEgMTcuNzU2OEM3Mi4yMTg5IDE3LjkxNzYgNzIuMTE1MiAxOC4xMDM4IDcyLjAzOTEgMTguMzE1NEM3MS45NjcxIDE4LjUyNyA3MS45MTg1IDE4Ljc1NTUgNzEuODkzMSAxOS4wMDFWMjAuMTQ5OUM3MS45MzEyIDIwLjQ2NzMgNzIuMDExNiAyMC43NTkzIDcyLjEzNDMgMjEuMDI1OUM3Mi4yNjEyIDIxLjI4ODIgNzIuNDM5IDIxLjQ5OTggNzIuNjY3NSAyMS42NjA2QzcyLjg5NiAyMS44MTcyIDczLjE5MDEgMjEuODk1NSA3My41NDk4IDIxLjg5NTVDNzMuODMzMyAyMS44OTU1IDc0LjA3MjQgMjEuODM4NCA3NC4yNjcxIDIxLjcyNDFDNzQuNDYxOCAyMS42MDk5IDc0LjYxNjIgMjEuNDUxMiA3NC43MzA1IDIxLjI0OEM3NC44NDkgMjEuMDQwNyA3NC45MzM2IDIwLjc5OTUgNzQuOTg0NCAyMC41MjQ0Qzc1LjAzOTQgMjAuMjQ5MyA3NS4wNjY5IDE5Ljk1MzEgNzUuMDY2OSAxOS42MzU3Wk04Mi4wMTM3IDIxLjYyMjZWMTguMzQ3MkM4Mi4wMTM3IDE4LjEwMTcgODEuOTY5MiAxNy44OTAxIDgxLjg4MDQgMTcuNzEyNEM4MS43OTE1IDE3LjUzNDcgODEuNjU2MSAxNy4zOTcxIDgxLjQ3NDEgMTcuMjk5OEM4MS4yOTY0IDE3LjIwMjUgODEuMDcyMSAxNy4xNTM4IDgwLjgwMTMgMTcuMTUzOEM4MC41NTE2IDE3LjE1MzggODAuMzM1OCAxNy4xOTYxIDgwLjE1MzggMTcuMjgwOEM3OS45NzE4IDE3LjM2NTQgNzkuODMwMSAxNy40Nzk3IDc5LjcyODUgMTcuNjIzNUM3OS42MjcgMTcuNzY3NCA3OS41NzYyIDE3LjkzMDMgNzkuNTc2MiAxOC4xMTIzSDc4LjA1MjdDNzguMDUyNyAxNy44NDE1IDc4LjExODMgMTcuNTc5MSA3OC4yNDk1IDE3LjMyNTJDNzguMzgwNyAxNy4wNzEzIDc4LjU3MTEgMTYuODQ0OSA3OC44MjA4IDE2LjY0NkM3OS4wNzA1IDE2LjQ0NzEgNzkuMzY4OCAxNi4yOTA1IDc5LjcxNTggMTYuMTc2M0M4MC4wNjI4IDE2LjA2MiA4MC40NTIxIDE2LjAwNDkgODAuODgzOCAxNi4wMDQ5QzgxLjQwMDEgMTYuMDA0OSA4MS44NTcxIDE2LjA5MTYgODIuMjU0OSAxNi4yNjUxQzgyLjY1NjkgMTYuNDM4NiA4Mi45NzIyIDE2LjcwMSA4My4yMDA3IDE3LjA1MjJDODMuNDMzNCAxNy4zOTkzIDgzLjU0OTggMTcuODM1MSA4My41NDk4IDE4LjM1OTlWMjEuNDEzMUM4My41NDk4IDIxLjcyNjIgODMuNTcxIDIyLjAwNzYgODMuNjEzMyAyMi4yNTczQzgzLjY1OTggMjIuNTAyOCA4My43MjU0IDIyLjcxNjUgODMuODEwMSAyMi44OTg0VjIzSDgyLjI0MjJDODIuMTcwMiAyMi44MzUgODIuMTEzMSAyMi42MjU1IDgyLjA3MDggMjIuMzcxNkM4Mi4wMzI3IDIyLjExMzQgODIuMDEzNyAyMS44NjM4IDgyLjAxMzcgMjEuNjIyNlpNODIuMjM1OCAxOC44MjMyTDgyLjI0ODUgMTkuNzY5SDgxLjE1MDRDODAuODY2OSAxOS43NjkgODAuNjE3MiAxOS43OTY1IDgwLjQwMTQgMTkuODUxNkM4MC4xODU1IDE5LjkwMjMgODAuMDA1NyAxOS45Nzg1IDc5Ljg2MTggMjAuMDgwMUM3OS43MTc5IDIwLjE4MTYgNzkuNjEgMjAuMzA0NCA3OS41MzgxIDIwLjQ0ODJDNzkuNDY2MSAyMC41OTIxIDc5LjQzMDIgMjAuNzU1IDc5LjQzMDIgMjAuOTM3Qzc5LjQzMDIgMjEuMTE5IDc5LjQ3MjUgMjEuMjg2MSA3OS41NTcxIDIxLjQzODVDNzkuNjQxOCAyMS41ODY2IDc5Ljc2NDUgMjEuNzAzIDc5LjkyNTMgMjEuNzg3NkM4MC4wOTAzIDIxLjg3MjIgODAuMjg5MiAyMS45MTQ2IDgwLjUyMiAyMS45MTQ2QzgwLjgzNTEgMjEuOTE0NiA4MS4xMDgxIDIxLjg1MTEgODEuMzQwOCAyMS43MjQxQzgxLjU3NzggMjEuNTkyOSA4MS43NjQgMjEuNDM0MiA4MS44OTk0IDIxLjI0OEM4Mi4wMzQ4IDIxLjA1NzYgODIuMTA2OCAyMC44Nzc4IDgyLjExNTIgMjAuNzA4NUw4Mi42MTA0IDIxLjM4NzdDODIuNTU5NiAyMS41NjEyIDgyLjQ3MjggMjEuNzQ3NCA4Mi4zNTAxIDIxLjk0NjNDODIuMjI3NCAyMi4xNDUyIDgyLjA2NjYgMjIuMzM1NiA4MS44Njc3IDIyLjUxNzZDODEuNjczIDIyLjY5NTMgODEuNDM4MiAyMi44NDEzIDgxLjE2MzEgMjIuOTU1NkM4MC44OTIzIDIzLjA2OTggODAuNTc5MSAyMy4xMjcgODAuMjIzNiAyMy4xMjdDNzkuNzc1MSAyMy4xMjcgNzkuMzc1MiAyMy4wMzgxIDc5LjAyMzkgMjIuODYwNEM3OC42NzI3IDIyLjY3ODQgNzguMzk3NiAyMi40MzUxIDc4LjE5ODcgMjIuMTMwNEM3Ny45OTk4IDIxLjgyMTUgNzcuOTAwNCAyMS40NzIzIDc3LjkwMDQgMjEuMDgzQzc3LjkwMDQgMjAuNzE5MSA3Ny45NjgxIDIwLjM5NzUgNzguMTAzNSAyMC4xMTgyQzc4LjI0MzIgMTkuODM0NiA3OC40NDYzIDE5LjU5NzcgNzguNzEyOSAxOS40MDcyQzc4Ljk4MzcgMTkuMjE2OCA3OS4zMTM4IDE5LjA3MjkgNzkuNzAzMSAxOC45NzU2QzgwLjA5MjQgMTguODc0IDgwLjUzNjggMTguODIzMiA4MS4wMzYxIDE4LjgyMzJIODIuMjM1OFpNODYuOTM1NSAxNy40Mzk1VjIzSDg1LjQwNThWMTYuMTMxOEg4Ni44NjU3TDg2LjkzNTUgMTcuNDM5NVpNODkuMDM2NiAxNi4wODc0TDg5LjAyMzkgMTcuNTA5M0M4OC45MzA4IDE3LjQ5MjQgODguODI5MyAxNy40Nzk3IDg4LjcxOTIgMTcuNDcxMkM4OC42MTM0IDE3LjQ2MjcgODguNTA3NiAxNy40NTg1IDg4LjQwMTkgMTcuNDU4NUM4OC4xMzk1IDE3LjQ1ODUgODcuOTA4OSAxNy40OTY2IDg3LjcxIDE3LjU3MjhDODcuNTExMSAxNy42NDQ3IDg3LjM0MzkgMTcuNzUwNSA4Ny4yMDg1IDE3Ljg5MDFDODcuMDc3MyAxOC4wMjU2IDg2Ljk3NTcgMTguMTkwNiA4Ni45MDM4IDE4LjM4NTNDODYuODMxOSAxOC41Nzk5IDg2Ljc4OTYgMTguNzk3OSA4Ni43NzY5IDE5LjAzOTFMODYuNDI3NyAxOS4wNjQ1Qzg2LjQyNzcgMTguNjMyOCA4Ni40NzAxIDE4LjIzMjkgODYuNTU0NyAxNy44NjQ3Qzg2LjYzOTMgMTcuNDk2NiA4Ni43NjYzIDE3LjE3MjkgODYuOTM1NSAxNi44OTM2Qzg3LjEwOSAxNi42MTQzIDg3LjMyNDkgMTYuMzk2MyA4Ny41ODMgMTYuMjM5N0M4Ny44NDU0IDE2LjA4MzIgODguMTQ3OSAxNi4wMDQ5IDg4LjQ5MDcgMTYuMDA0OUM4OC41ODM4IDE2LjAwNDkgODguNjgzMyAxNi4wMTMzIDg4Ljc4OTEgMTYuMDMwM0M4OC44OTkxIDE2LjA0NzIgODguOTgxNiAxNi4wNjYyIDg5LjAzNjYgMTYuMDg3NFoiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTE2LjYwNTUgOTcuMTkxNEgxOC4yOTNDMTguOTQ5MiA5Ny4xOTE0IDE5LjQ5MjIgOTcuMDc4MSAxOS45MjE5IDk2Ljg1MTZDMjAuMzU5NCA5Ni42MjUgMjAuNjgzNiA5Ni4zMTI1IDIwLjg5NDUgOTUuOTE0MUMyMS4xMDU1IDk1LjUxNTYgMjEuMjEwOSA5NS4wNTg2IDIxLjIxMDkgOTQuNTQzQzIxLjIxMDkgOTQuMDAzOSAyMS4xMTMzIDkzLjU0MyAyMC45MTggOTMuMTYwMkMyMC43MzA1IDkyLjc2OTUgMjAuNDQxNCA5Mi40Njg4IDIwLjA1MDggOTIuMjU3OEMxOS42NjggOTIuMDQ2OSAxOS4xNzk3IDkxLjk0MTQgMTguNTg1OSA5MS45NDE0QzE4LjA4NTkgOTEuOTQxNCAxNy42MzI4IDkyLjA0MyAxNy4yMjY2IDkyLjI0NjFDMTYuODI4MSA5Mi40NDE0IDE2LjUxMTcgOTIuNzIyNyAxNi4yNzczIDkzLjA4OThDMTYuMDQzIDkzLjQ0OTIgMTUuOTI1OCA5My44Nzg5IDE1LjkyNTggOTQuMzc4OUgxMy4wODk4QzEzLjA4OTggOTMuNDcyNyAxMy4zMjgxIDkyLjY2OCAxMy44MDQ3IDkxLjk2NDhDMTQuMjgxMiA5MS4yNjE3IDE0LjkyOTcgOTAuNzEwOSAxNS43NSA5MC4zMTI1QzE2LjU3ODEgODkuOTA2MiAxNy41MDc4IDg5LjcwMzEgMTguNTM5MSA4OS43MDMxQzE5LjY0MDYgODkuNzAzMSAyMC42MDE2IDg5Ljg4NjcgMjEuNDIxOSA5MC4yNTM5QzIyLjI1IDkwLjYxMzMgMjIuODk0NSA5MS4xNTIzIDIzLjM1NTUgOTEuODcxMUMyMy44MTY0IDkyLjU4OTggMjQuMDQ2OSA5My40ODA1IDI0LjA0NjkgOTQuNTQzQzI0LjA0NjkgOTUuMDI3MyAyMy45MzM2IDk1LjUxOTUgMjMuNzA3IDk2LjAxOTVDMjMuNDgwNSA5Ni41MTk1IDIzLjE0NDUgOTYuOTc2NiAyMi42OTkyIDk3LjM5MDZDMjIuMjUzOSA5Ny43OTY5IDIxLjY5OTIgOTguMTI4OSAyMS4wMzUyIDk4LjM4NjdDMjAuMzcxMSA5OC42MzY3IDE5LjYwMTYgOTguNzYxNyAxOC43MjY2IDk4Ljc2MTdIMTYuNjA1NVY5Ny4xOTE0Wk0xNi42MDU1IDk5LjM5NDVWOTcuODQ3N0gxOC43MjY2QzE5LjcyNjYgOTcuODQ3NyAyMC41NzgxIDk3Ljk2NDggMjEuMjgxMiA5OC4xOTkyQzIxLjk5MjIgOTguNDMzNiAyMi41NzAzIDk4Ljc1NzggMjMuMDE1NiA5OS4xNzE5QzIzLjQ2MDkgOTkuNTc4MSAyMy43ODUyIDEwMC4wNDMgMjMuOTg4MyAxMDAuNTY2QzI0LjE5OTIgMTAxLjA5IDI0LjMwNDcgMTAxLjY0NSAyNC4zMDQ3IDEwMi4yM0MyNC4zMDQ3IDEwMy4wMjcgMjQuMTYwMiAxMDMuNzM4IDIzLjg3MTEgMTA0LjM2M0MyMy41ODk4IDEwNC45OCAyMy4xODc1IDEwNS41MDQgMjIuNjY0MSAxMDUuOTM0QzIyLjE0MDYgMTA2LjM2MyAyMS41MjczIDEwNi42ODggMjAuODI0MiAxMDYuOTA2QzIwLjEyODkgMTA3LjEyNSAxOS4zNzExIDEwNy4yMzQgMTguNTUwOCAxMDcuMjM0QzE3LjgxNjQgMTA3LjIzNCAxNy4xMTMzIDEwNy4xMzMgMTYuNDQxNCAxMDYuOTNDMTUuNzY5NSAxMDYuNzI3IDE1LjE2OCAxMDYuNDI2IDE0LjYzNjcgMTA2LjAyN0MxNC4xMDU1IDEwNS42MjEgMTMuNjgzNiAxMDUuMTE3IDEzLjM3MTEgMTA0LjUxNkMxMy4wNjY0IDEwMy45MDYgMTIuOTE0MSAxMDMuMjAzIDEyLjkxNDEgMTAyLjQwNkgxNS43MzgzQzE1LjczODMgMTAyLjkxNCAxNS44NTU1IDEwMy4zNjMgMTYuMDg5OCAxMDMuNzU0QzE2LjMzMiAxMDQuMTM3IDE2LjY2OCAxMDQuNDM4IDE3LjA5NzcgMTA0LjY1NkMxNy41MzUyIDEwNC44NzUgMTguMDM1MiAxMDQuOTg0IDE4LjU5NzcgMTA0Ljk4NEMxOS4xOTE0IDEwNC45ODQgMTkuNzAzMSAxMDQuODc5IDIwLjEzMjggMTA0LjY2OEMyMC41NjI1IDEwNC40NTcgMjAuODkwNiAxMDQuMTQ1IDIxLjExNzIgMTAzLjczQzIxLjM1MTYgMTAzLjMxNiAyMS40Njg4IDEwMi44MTYgMjEuNDY4OCAxMDIuMjNDMjEuNDY4OCAxMDEuNTY2IDIxLjMzOTggMTAxLjAyNyAyMS4wODIgMTAwLjYxM0MyMC44MjQyIDEwMC4xOTkgMjAuNDU3IDk5Ljg5NDUgMTkuOTgwNSA5OS42OTkyQzE5LjUwMzkgOTkuNDk2MSAxOC45NDE0IDk5LjM5NDUgMTguMjkzIDk5LjM5NDVIMTYuNjA1NVpNMzUuNDYwOSA4OS44MzJIMzUuODEyNVY5Mi4xNDA2SDM1LjYxMzNDMzQuNjA1NSA5Mi4xNDA2IDMzLjczNDQgOTIuMjk2OSAzMyA5Mi42MDk0QzMyLjI3MzQgOTIuOTIxOSAzMS42NzU4IDkzLjM1MTYgMzEuMjA3IDkzLjg5ODRDMzAuNzM4MyA5NC40NDUzIDMwLjM4NjcgOTUuMDg1OSAzMC4xNTIzIDk1LjgyMDNDMjkuOTI1OCA5Ni41NDY5IDI5LjgxMjUgOTcuMzIwMyAyOS44MTI1IDk4LjE0MDZWMTAwLjgyNEMyOS44MTI1IDEwMS41MDQgMjkuODg2NyAxMDIuMTA1IDMwLjAzNTIgMTAyLjYyOUMzMC4xODM2IDEwMy4xNDUgMzAuMzkwNiAxMDMuNTc4IDMwLjY1NjIgMTAzLjkzQzMwLjkyOTcgMTA0LjI3MyAzMS4yNDIyIDEwNC41MzUgMzEuNTkzOCAxMDQuNzE1QzMxLjk0NTMgMTA0Ljg5NSAzMi4zMjQyIDEwNC45ODQgMzIuNzMwNSAxMDQuOTg0QzMzLjE1MjMgMTA0Ljk4NCAzMy41MzUyIDEwNC44OTggMzMuODc4OSAxMDQuNzI3QzM0LjIyMjcgMTA0LjU0NyAzNC41MTU2IDEwNC4zMDEgMzQuNzU3OCAxMDMuOTg4QzM1IDEwMy42NzYgMzUuMTgzNiAxMDMuMzA1IDM1LjMwODYgMTAyLjg3NUMzNS40MzM2IDEwMi40NDUgMzUuNDk2MSAxMDEuOTc3IDM1LjQ5NjEgMTAxLjQ2OUMzNS40OTYxIDEwMC45ODQgMzUuNDMzNiAxMDAuNTMxIDM1LjMwODYgMTAwLjEwOUMzNS4xOTE0IDk5LjY3OTcgMzUuMDE1NiA5OS4zMDQ3IDM0Ljc4MTIgOTguOTg0NEMzNC41NDY5IDk4LjY1NjIgMzQuMjUzOSA5OC40MDIzIDMzLjkwMjMgOTguMjIyN0MzMy41NTg2IDk4LjAzNTIgMzMuMTYwMiA5Ny45NDE0IDMyLjcwNyA5Ny45NDE0QzMyLjE0NDUgOTcuOTQxNCAzMS42MzI4IDk4LjA3NDIgMzEuMTcxOSA5OC4zMzk4QzMwLjcxODggOTguNjA1NSAzMC4zNTE2IDk4Ljk1MzEgMzAuMDcwMyA5OS4zODI4QzI5Ljc5NjkgOTkuODA0NyAyOS42NDg0IDEwMC4yNTQgMjkuNjI1IDEwMC43M0wyOC41NDY5IDEwMC4zNzlDMjguNjA5NCA5OS42NTIzIDI4Ljc2OTUgOTkgMjkuMDI3MyA5OC40MjE5QzI5LjI5MyA5Ny44NDM4IDI5LjY0MDYgOTcuMzUxNiAzMC4wNzAzIDk2Ljk0NTNDMzAuNSA5Ni41MzkxIDMwLjk5NjEgOTYuMjMwNSAzMS41NTg2IDk2LjAxOTVDMzIuMTI4OSA5NS44MDA4IDMyLjc1MzkgOTUuNjkxNCAzMy40MzM2IDk1LjY5MTRDMzQuMjYxNyA5NS42OTE0IDM0Ljk4MDUgOTUuODQ3NyAzNS41ODk4IDk2LjE2MDJDMzYuMTk5MiA5Ni40NzI3IDM2LjcwMzEgOTYuODk4NCAzNy4xMDE2IDk3LjQzNzVDMzcuNTA3OCA5Ny45Njg4IDM3LjgwODYgOTguNTc4MSAzOC4wMDM5IDk5LjI2NTZDMzguMjA3IDk5Ljk0NTMgMzguMzA4NiAxMDAuNjU2IDM4LjMwODYgMTAxLjM5OEMzOC4zMDg2IDEwMi4yMTkgMzguMTgzNiAxMDIuOTg0IDM3LjkzMzYgMTAzLjY5NUMzNy42ODM2IDEwNC4zOTggMzcuMzE2NCAxMDUuMDE2IDM2LjgzMiAxMDUuNTQ3QzM2LjM1NTUgMTA2LjA3OCAzNS43NzM0IDEwNi40OTIgMzUuMDg1OSAxMDYuNzg5QzM0LjQwNjIgMTA3LjA4NiAzMy42MzI4IDEwNy4yMzQgMzIuNzY1NiAxMDcuMjM0QzMxLjg1MTYgMTA3LjIzNCAzMS4wMzUyIDEwNy4wNTkgMzAuMzE2NCAxMDYuNzA3QzI5LjYwNTUgMTA2LjM1NSAyOSAxMDUuODcxIDI4LjUgMTA1LjI1NEMyOC4wMDc4IDEwNC42MzcgMjcuNjMyOCAxMDMuOTI2IDI3LjM3NSAxMDMuMTIxQzI3LjExNzIgMTAyLjMxNiAyNi45ODgzIDEwMS40NjUgMjYuOTg4MyAxMDAuNTY2Vjk5LjM5NDVDMjYuOTg4MyA5OC4wOTc3IDI3LjE1MjMgOTYuODc1IDI3LjQ4MDUgOTUuNzI2NkMyNy44MDg2IDk0LjU3MDMgMjguMzEyNSA5My41NTA4IDI4Ljk5MjIgOTIuNjY4QzI5LjY3OTcgOTEuNzg1MiAzMC41NTg2IDkxLjA5MzggMzEuNjI4OSA5MC41OTM4QzMyLjY5OTIgOTAuMDg1OSAzMy45NzY2IDg5LjgzMiAzNS40NjA5IDg5LjgzMloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTQyLjcxNzggOTkuNDg3M1Y5OC45NjA5QzQyLjcxNzggOTguNTgyNyA0Mi43OTk4IDk4LjIzODYgNDIuOTYzOSA5Ny45Mjg3QzQzLjEyNzkgOTcuNjE4OCA0My4zNjI2IDk3LjM3MDQgNDMuNjY4IDk3LjE4MzZDNDMuOTczMyA5Ni45OTY3IDQ0LjMzNTYgOTYuOTAzMyA0NC43NTQ5IDk2LjkwMzNDNDUuMTgzMyA5Ni45MDMzIDQ1LjU0NzkgOTYuOTk2NyA0NS44NDg2IDk3LjE4MzZDNDYuMTU0IDk3LjM3MDQgNDYuMzg4NyA5Ny42MTg4IDQ2LjU1MjcgOTcuOTI4N0M0Ni43MTY4IDk4LjIzODYgNDYuNzk4OCA5OC41ODI3IDQ2Ljc5ODggOTguOTYwOVY5OS40ODczQzQ2Ljc5ODggOTkuODU2NCA0Ni43MTY4IDEwMC4xOTYgNDYuNTUyNyAxMDAuNTA2QzQ2LjM5MzIgMTAwLjgxNiA0Ni4xNjA4IDEwMS4wNjQgNDUuODU1NSAxMDEuMjUxQzQ1LjU1NDcgMTAxLjQzOCA0NS4xOTI0IDEwMS41MzEgNDQuNzY4NiAxMDEuNTMxQzQ0LjM0NDcgMTAxLjUzMSA0My45Nzc5IDEwMS40MzggNDMuNjY4IDEwMS4yNTFDNDMuMzYyNiAxMDEuMDY0IDQzLjEyNzkgMTAwLjgxNiA0Mi45NjM5IDEwMC41MDZDNDIuNzk5OCAxMDAuMTk2IDQyLjcxNzggOTkuODU2NCA0Mi43MTc4IDk5LjQ4NzNaTTQzLjY2OCA5OC45NjA5Vjk5LjQ4NzNDNDMuNjY4IDk5LjY5NjkgNDMuNzA2NyA5OS44OTUyIDQzLjc4NDIgMTAwLjA4MkM0My44NjYyIDEwMC4yNjkgNDMuOTg5MyAxMDAuNDIyIDQ0LjE1MzMgMTAwLjU0QzQ0LjMxNzQgMTAwLjY1NCA0NC41MjI1IDEwMC43MTEgNDQuNzY4NiAxMDAuNzExQzQ1LjAxNDYgMTAwLjcxMSA0NS4yMTc0IDEwMC42NTQgNDUuMzc3IDEwMC41NEM0NS41MzY1IDEwMC40MjIgNDUuNjU0OSAxMDAuMjY5IDQ1LjczMjQgMTAwLjA4MkM0NS44MDk5IDk5Ljg5NTIgNDUuODQ4NiA5OS42OTY5IDQ1Ljg0ODYgOTkuNDg3M1Y5OC45NjA5QzQ1Ljg0ODYgOTguNzQ2NyA0NS44MDc2IDk4LjU0NjIgNDUuNzI1NiA5OC4zNTk0QzQ1LjY0ODEgOTguMTY4IDQ1LjUyNzMgOTguMDE1MyA0NS4zNjMzIDk3LjkwMTRDNDUuMjAzOCA5Ny43ODI5IDQ1LjAwMSA5Ny43MjM2IDQ0Ljc1NDkgOTcuNzIzNkM0NC41MTMzIDk3LjcyMzYgNDQuMzEwNSA5Ny43ODI5IDQ0LjE0NjUgOTcuOTAxNEM0My45ODcgOTguMDE1MyA0My44NjYyIDk4LjE2OCA0My43ODQyIDk4LjM1OTRDNDMuNzA2NyA5OC41NDYyIDQzLjY2OCA5OC43NDY3IDQzLjY2OCA5OC45NjA5Wk00Ny41NjQ1IDEwNS4wOTNWMTA0LjU2QzQ3LjU2NDUgMTA0LjE4NiA0Ny42NDY1IDEwMy44NDQgNDcuODEwNSAxMDMuNTM0QzQ3Ljk3NDYgMTAzLjIyNCA0OC4yMDkzIDEwMi45NzYgNDguNTE0NiAxMDIuNzg5QzQ4LjgyIDEwMi42MDIgNDkuMTgyMyAxMDIuNTA5IDQ5LjYwMTYgMTAyLjUwOUM1MC4wMjk5IDEwMi41MDkgNTAuMzk0NSAxMDIuNjAyIDUwLjY5NTMgMTAyLjc4OUM1MS4wMDA3IDEwMi45NzYgNTEuMjM1NCAxMDMuMjI0IDUxLjM5OTQgMTAzLjUzNEM1MS41NjM1IDEwMy44NDQgNTEuNjQ1NSAxMDQuMTg2IDUxLjY0NTUgMTA0LjU2VjEwNS4wOTNDNTEuNjQ1NSAxMDUuNDY2IDUxLjU2MzUgMTA1LjgwOCA1MS4zOTk0IDEwNi4xMThDNTEuMjM5OSAxMDYuNDI4IDUxLjAwNzUgMTA2LjY3NiA1MC43MDIxIDEwNi44NjNDNTAuNDAxNCAxMDcuMDUgNTAuMDM5MSAxMDcuMTQ0IDQ5LjYxNTIgMTA3LjE0NEM0OS4xOTE0IDEwNy4xNDQgNDguODI2OCAxMDcuMDUgNDguNTIxNSAxMDYuODYzQzQ4LjIxNjEgMTA2LjY3NiA0Ny45NzkyIDEwNi40MjggNDcuODEwNSAxMDYuMTE4QzQ3LjY0NjUgMTA1LjgwOCA0Ny41NjQ1IDEwNS40NjYgNDcuNTY0NSAxMDUuMDkzWk00OC41MTQ2IDEwNC41NlYxMDUuMDkzQzQ4LjUxNDYgMTA1LjMwMiA0OC41NTM0IDEwNS41MDMgNDguNjMwOSAxMDUuNjk0QzQ4LjcxMjkgMTA1Ljg4MSA0OC44MzU5IDEwNi4wMzQgNDkgMTA2LjE1MkM0OS4xNjQxIDEwNi4yNjYgNDkuMzY5MSAxMDYuMzIzIDQ5LjYxNTIgMTA2LjMyM0M0OS44NjEzIDEwNi4zMjMgNTAuMDY0MSAxMDYuMjY2IDUwLjIyMzYgMTA2LjE1MkM1MC4zODc3IDEwNi4wMzQgNTAuNTA4NSAxMDUuODgxIDUwLjU4NTkgMTA1LjY5NEM1MC42NjM0IDEwNS41MDcgNTAuNzAyMSAxMDUuMzA3IDUwLjcwMjEgMTA1LjA5M1YxMDQuNTZDNTAuNzAyMSAxMDQuMzQ1IDUwLjY2MTEgMTA0LjE0NSA1MC41NzkxIDEwMy45NThDNTAuNTAxNiAxMDMuNzcxIDUwLjM4MDkgMTAzLjYyMSA1MC4yMTY4IDEwMy41MDdDNTAuMDU3MyAxMDMuMzg4IDQ5Ljg1MjIgMTAzLjMyOSA0OS42MDE2IDEwMy4zMjlDNDkuMzYgMTAzLjMyOSA0OS4xNTcyIDEwMy4zODggNDguOTkzMiAxMDMuNTA3QzQ4LjgzMzcgMTAzLjYyMSA0OC43MTI5IDEwMy43NzEgNDguNjMwOSAxMDMuOTU4QzQ4LjU1MzQgMTA0LjE0NSA0OC41MTQ2IDEwNC4zNDUgNDguNTE0NiAxMDQuNTZaTTQ5LjkxNiA5OC40NjE5TDQ1LjA1NTcgMTA2LjI0MUw0NC4zNDQ3IDEwNS43OUw0OS4yMDUxIDk4LjAxMDdMNDkuOTE2IDk4LjQ2MTlaIiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg3Ii8+CjxyZWN0IHg9IjEyIiB5PSIxMjMiIHdpZHRoPSIxNzYiIGhlaWdodD0iNSIgcng9IjIuNSIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC4wNCIvPgo8cmVjdCB3aWR0aD0iNTEiIGhlaWdodD0iNSIgcng9IjIuNSIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgNjMgMTIzKSIgZmlsbD0iIzNGNTJERCIvPgo8cGF0aCBkPSJNMTcuNTU5MSAxMzkuNDY3VjE0MC42NTlDMTcuNTU5MSAxNDEuMyAxNy41MDE4IDE0MS44NDEgMTcuMzg3MiAxNDIuMjgxQzE3LjI3MjYgMTQyLjcyMiAxNy4xMDc5IDE0My4wNzYgMTYuODkzMSAxNDMuMzQ1QzE2LjY3ODIgMTQzLjYxMyAxNi40MTg2IDE0My44MDggMTYuMTE0MyAxNDMuOTNDMTUuODEzNSAxNDQuMDQ4IDE1LjQ3MzMgMTQ0LjEwNyAxNS4wOTM4IDE0NC4xMDdDMTQuNzkzIDE0NC4xMDcgMTQuNTE1NSAxNDQuMDcgMTQuMjYxMiAxNDMuOTk1QzE0LjAwNyAxNDMuOTE5IDEzLjc3NzggMTQzLjc5OSAxMy41NzM3IDE0My42MzVDMTMuMzczMiAxNDMuNDY2IDEzLjIwMTMgMTQzLjI0OCAxMy4wNTgxIDE0Mi45NzlDMTIuOTE0OSAxNDIuNzExIDEyLjgwNTcgMTQyLjM4NSAxMi43MzA1IDE0Mi4wMDJDMTIuNjU1MyAxNDEuNjE5IDEyLjYxNzcgMTQxLjE3MSAxMi42MTc3IDE0MC42NTlWMTM5LjQ2N0MxMi42MTc3IDEzOC44MjYgMTIuNjc1IDEzOC4yODkgMTIuNzg5NiAxMzcuODU1QzEyLjkwNzcgMTM3LjQyMiAxMy4wNzQyIDEzNy4wNzUgMTMuMjg5MSAxMzYuODEzQzEzLjUwMzkgMTM2LjU0OSAxMy43NjE3IDEzNi4zNTkgMTQuMDYyNSAxMzYuMjQ0QzE0LjM2NjkgMTM2LjEzIDE0LjcwNyAxMzYuMDcyIDE1LjA4MyAxMzYuMDcyQzE1LjM4NzQgMTM2LjA3MiAxNS42NjY3IDEzNi4xMSAxNS45MjA5IDEzNi4xODVDMTYuMTc4NyAxMzYuMjU3IDE2LjQwNzkgMTM2LjM3MyAxNi42MDg0IDEzNi41MzRDMTYuODA4OSAxMzYuNjkyIDE2Ljk3OSAxMzYuOTAzIDE3LjExODcgMTM3LjE2OEMxNy4yNjE5IDEzNy40MjkgMTcuMzcxMSAxMzcuNzUgMTcuNDQ2MyAxMzguMTI5QzE3LjUyMTUgMTM4LjUwOSAxNy41NTkxIDEzOC45NTUgMTcuNTU5MSAxMzkuNDY3Wk0xNi41NjAxIDE0MC44MlYxMzkuM0MxNi41NjAxIDEzOC45NDkgMTYuNTM4NiAxMzguNjQxIDE2LjQ5NTYgMTM4LjM3NkMxNi40NTYyIDEzOC4xMDggMTYuMzk3MSAxMzcuODc5IDE2LjMxODQgMTM3LjY4OUMxNi4yMzk2IDEzNy40OTkgMTYuMTM5MyAxMzcuMzQ1IDE2LjAxNzYgMTM3LjIyN0MxNS44OTk0IDEzNy4xMDkgMTUuNzYxNiAxMzcuMDIzIDE1LjYwNCAxMzYuOTY5QzE1LjQ1IDEzNi45MTIgMTUuMjc2NCAxMzYuODgzIDE1LjA4MyAxMzYuODgzQzE0Ljg0NjcgMTM2Ljg4MyAxNC42MzcyIDEzNi45MjggMTQuNDU0NiAxMzcuMDE4QzE0LjI3MiAxMzcuMTA0IDE0LjExOCAxMzcuMjQxIDEzLjk5MjcgMTM3LjQzMUMxMy44NzA5IDEzNy42MjEgMTMuNzc3OCAxMzcuODcgMTMuNzEzNCAxMzguMTc4QzEzLjY0ODkgMTM4LjQ4NiAxMy42MTY3IDEzOC44NiAxMy42MTY3IDEzOS4zVjE0MC44MkMxMy42MTY3IDE0MS4xNzEgMTMuNjM2NCAxNDEuNDgxIDEzLjY3NTggMTQxLjc1QzEzLjcxODggMTQyLjAxOCAxMy43ODE0IDE0Mi4yNTEgMTMuODYzOCAxNDIuNDQ4QzEzLjk0NjEgMTQyLjY0MSAxNC4wNDY0IDE0Mi44IDE0LjE2NDYgMTQyLjkyNkMxNC4yODI3IDE0My4wNTEgMTQuNDE4OCAxNDMuMTQ0IDE0LjU3MjggMTQzLjIwNUMxNC43MzAzIDE0My4yNjIgMTQuOTA0IDE0My4yOTEgMTUuMDkzOCAxNDMuMjkxQzE1LjMzNzIgMTQzLjI5MSAxNS41NTAzIDE0My4yNDQgMTUuNzMyOSAxNDMuMTUxQzE1LjkxNTUgMTQzLjA1OCAxNi4wNjc3IDE0Mi45MTMgMTYuMTg5NSAxNDIuNzE2QzE2LjMxNDggMTQyLjUxNiAxNi40MDc5IDE0Mi4yNiAxNi40Njg4IDE0MS45NDhDMTYuNTI5NiAxNDEuNjMzIDE2LjU2MDEgMTQxLjI1NyAxNi41NjAxIDE0MC44MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPHBhdGggZD0iTTE3MS45MTYgMTM2LjEzN1YxNDRIMTcwLjkyMlYxMzcuMzc3TDE2OC45MTggMTM4LjEwOFYxMzcuMjExTDE3MS43NiAxMzYuMTM3SDE3MS45MTZaTTE3OS45OTcgMTM5LjQ2N1YxNDAuNjU5QzE3OS45OTcgMTQxLjMgMTc5LjkzOSAxNDEuODQxIDE3OS44MjUgMTQyLjI4MUMxNzkuNzEgMTQyLjcyMiAxNzkuNTQ1IDE0My4wNzYgMTc5LjMzMSAxNDMuMzQ1QzE3OS4xMTYgMTQzLjYxMyAxNzguODU2IDE0My44MDggMTc4LjU1MiAxNDMuOTNDMTc4LjI1MSAxNDQuMDQ4IDE3Ny45MTEgMTQ0LjEwNyAxNzcuNTMxIDE0NC4xMDdDMTc3LjIzIDE0NC4xMDcgMTc2Ljk1MyAxNDQuMDcgMTc2LjY5OSAxNDMuOTk1QzE3Ni40NDQgMTQzLjkxOSAxNzYuMjE1IDE0My43OTkgMTc2LjAxMSAxNDMuNjM1QzE3NS44MTEgMTQzLjQ2NiAxNzUuNjM5IDE0My4yNDggMTc1LjQ5NiAxNDIuOTc5QzE3NS4zNTIgMTQyLjcxMSAxNzUuMjQzIDE0Mi4zODUgMTc1LjE2OCAxNDIuMDAyQzE3NS4wOTMgMTQxLjYxOSAxNzUuMDU1IDE0MS4xNzEgMTc1LjA1NSAxNDAuNjU5VjEzOS40NjdDMTc1LjA1NSAxMzguODI2IDE3NS4xMTIgMTM4LjI4OSAxNzUuMjI3IDEzNy44NTVDMTc1LjM0NSAxMzcuNDIyIDE3NS41MTIgMTM3LjA3NSAxNzUuNzI3IDEzNi44MTNDMTc1Ljk0MSAxMzYuNTQ5IDE3Ni4xOTkgMTM2LjM1OSAxNzYuNSAxMzYuMjQ0QzE3Ni44MDQgMTM2LjEzIDE3Ny4xNDUgMTM2LjA3MiAxNzcuNTIxIDEzNi4wNzJDMTc3LjgyNSAxMzYuMDcyIDE3OC4xMDQgMTM2LjExIDE3OC4zNTggMTM2LjE4NUMxNzguNjE2IDEzNi4yNTcgMTc4Ljg0NSAxMzYuMzczIDE3OS4wNDYgMTM2LjUzNEMxNzkuMjQ2IDEzNi42OTIgMTc5LjQxNyAxMzYuOTAzIDE3OS41NTYgMTM3LjE2OEMxNzkuNjk5IDEzNy40MjkgMTc5LjgwOSAxMzcuNzUgMTc5Ljg4NCAxMzguMTI5QzE3OS45NTkgMTM4LjUwOSAxNzkuOTk3IDEzOC45NTUgMTc5Ljk5NyAxMzkuNDY3Wk0xNzguOTk4IDE0MC44MlYxMzkuM0MxNzguOTk4IDEzOC45NDkgMTc4Ljk3NiAxMzguNjQxIDE3OC45MzMgMTM4LjM3NkMxNzguODk0IDEzOC4xMDggMTc4LjgzNSAxMzcuODc5IDE3OC43NTYgMTM3LjY4OUMxNzguNjc3IDEzNy40OTkgMTc4LjU3NyAxMzcuMzQ1IDE3OC40NTUgMTM3LjIyN0MxNzguMzM3IDEzNy4xMDkgMTc4LjE5OSAxMzcuMDIzIDE3OC4wNDIgMTM2Ljk2OUMxNzcuODg4IDEzNi45MTIgMTc3LjcxNCAxMzYuODgzIDE3Ny41MjEgMTM2Ljg4M0MxNzcuMjg0IDEzNi44ODMgMTc3LjA3NSAxMzYuOTI4IDE3Ni44OTIgMTM3LjAxOEMxNzYuNzA5IDEzNy4xMDQgMTc2LjU1NiAxMzcuMjQxIDE3Ni40MyAxMzcuNDMxQzE3Ni4zMDggMTM3LjYyMSAxNzYuMjE1IDEzNy44NyAxNzYuMTUxIDEzOC4xNzhDMTc2LjA4NiAxMzguNDg2IDE3Ni4wNTQgMTM4Ljg2IDE3Ni4wNTQgMTM5LjNWMTQwLjgyQzE3Ni4wNTQgMTQxLjE3MSAxNzYuMDc0IDE0MS40ODEgMTc2LjExMyAxNDEuNzVDMTc2LjE1NiAxNDIuMDE4IDE3Ni4yMTkgMTQyLjI1MSAxNzYuMzAxIDE0Mi40NDhDMTc2LjM4NCAxNDIuNjQxIDE3Ni40ODQgMTQyLjggMTc2LjYwMiAxNDIuOTI2QzE3Ni43MiAxNDMuMDUxIDE3Ni44NTYgMTQzLjE0NCAxNzcuMDEgMTQzLjIwNUMxNzcuMTY4IDE0My4yNjIgMTc3LjM0MSAxNDMuMjkxIDE3Ny41MzEgMTQzLjI5MUMxNzcuNzc1IDE0My4yOTEgMTc3Ljk4OCAxNDMuMjQ0IDE3OC4xNyAxNDMuMTUxQzE3OC4zNTMgMTQzLjA1OCAxNzguNTA1IDE0Mi45MTMgMTc4LjYyNyAxNDIuNzE2QzE3OC43NTIgMTQyLjUxNiAxNzguODQ1IDE0Mi4yNiAxNzguOTA2IDE0MS45NDhDMTc4Ljk2NyAxNDEuNjMzIDE3OC45OTggMTQxLjI1NyAxNzguOTk4IDE0MC44MlpNMTg2LjQzNCAxMzkuNDY3VjE0MC42NTlDMTg2LjQzNCAxNDEuMyAxODYuMzc3IDE0MS44NDEgMTg2LjI2MiAxNDIuMjgxQzE4Ni4xNDggMTQyLjcyMiAxODUuOTgzIDE0My4wNzYgMTg1Ljc2OCAxNDMuMzQ1QzE4NS41NTMgMTQzLjYxMyAxODUuMjk0IDE0My44MDggMTg0Ljk4OSAxNDMuOTNDMTg0LjY4OCAxNDQuMDQ4IDE4NC4zNDggMTQ0LjEwNyAxODMuOTY5IDE0NC4xMDdDMTgzLjY2OCAxNDQuMTA3IDE4My4zOSAxNDQuMDcgMTgzLjEzNiAxNDMuOTk1QzE4Mi44ODIgMTQzLjkxOSAxODIuNjUzIDE0My43OTkgMTgyLjQ0OSAxNDMuNjM1QzE4Mi4yNDggMTQzLjQ2NiAxODIuMDc2IDE0My4yNDggMTgxLjkzMyAxNDIuOTc5QzE4MS43OSAxNDIuNzExIDE4MS42ODEgMTQyLjM4NSAxODEuNjA1IDE0Mi4wMDJDMTgxLjUzIDE0MS42MTkgMTgxLjQ5MyAxNDEuMTcxIDE4MS40OTMgMTQwLjY1OVYxMzkuNDY3QzE4MS40OTMgMTM4LjgyNiAxODEuNTUgMTM4LjI4OSAxODEuNjY1IDEzNy44NTVDMTgxLjc4MyAxMzcuNDIyIDE4MS45NDkgMTM3LjA3NSAxODIuMTY0IDEzNi44MTNDMTgyLjM3OSAxMzYuNTQ5IDE4Mi42MzcgMTM2LjM1OSAxODIuOTM4IDEzNi4yNDRDMTgzLjI0MiAxMzYuMTMgMTgzLjU4MiAxMzYuMDcyIDE4My45NTggMTM2LjA3MkMxODQuMjYyIDEzNi4wNzIgMTg0LjU0MiAxMzYuMTEgMTg0Ljc5NiAxMzYuMTg1QzE4NS4wNTQgMTM2LjI1NyAxODUuMjgzIDEzNi4zNzMgMTg1LjQ4MyAxMzYuNTM0QzE4NS42ODQgMTM2LjY5MiAxODUuODU0IDEzNi45MDMgMTg1Ljk5NCAxMzcuMTY4QzE4Ni4xMzcgMTM3LjQyOSAxODYuMjQ2IDEzNy43NSAxODYuMzIxIDEzOC4xMjlDMTg2LjM5NiAxMzguNTA5IDE4Ni40MzQgMTM4Ljk1NSAxODYuNDM0IDEzOS40NjdaTTE4NS40MzUgMTQwLjgyVjEzOS4zQzE4NS40MzUgMTM4Ljk0OSAxODUuNDE0IDEzOC42NDEgMTg1LjM3MSAxMzguMzc2QzE4NS4zMzEgMTM4LjEwOCAxODUuMjcyIDEzNy44NzkgMTg1LjE5MyAxMzcuNjg5QzE4NS4xMTUgMTM3LjQ5OSAxODUuMDE0IDEzNy4zNDUgMTg0Ljg5MyAxMzcuMjI3QzE4NC43NzQgMTM3LjEwOSAxODQuNjM3IDEzNy4wMjMgMTg0LjQ3OSAxMzYuOTY5QzE4NC4zMjUgMTM2LjkxMiAxODQuMTUxIDEzNi44ODMgMTgzLjk1OCAxMzYuODgzQzE4My43MjIgMTM2Ljg4MyAxODMuNTEyIDEzNi45MjggMTgzLjMzIDEzNy4wMThDMTgzLjE0NyAxMzcuMTA0IDE4Mi45OTMgMTM3LjI0MSAxODIuODY4IDEzNy40MzFDMTgyLjc0NiAxMzcuNjIxIDE4Mi42NTMgMTM3Ljg3IDE4Mi41ODggMTM4LjE3OEMxODIuNTI0IDEzOC40ODYgMTgyLjQ5MiAxMzguODYgMTgyLjQ5MiAxMzkuM1YxNDAuODJDMTgyLjQ5MiAxNDEuMTcxIDE4Mi41MTEgMTQxLjQ4MSAxODIuNTUxIDE0MS43NUMxODIuNTk0IDE0Mi4wMTggMTgyLjY1NiAxNDIuMjUxIDE4Mi43MzkgMTQyLjQ0OEMxODIuODIxIDE0Mi42NDEgMTgyLjkyMSAxNDIuOCAxODMuMDQgMTQyLjkyNkMxODMuMTU4IDE0My4wNTEgMTgzLjI5NCAxNDMuMTQ0IDE4My40NDggMTQzLjIwNUMxODMuNjA1IDE0My4yNjIgMTgzLjc3OSAxNDMuMjkxIDE4My45NjkgMTQzLjI5MUMxODQuMjEyIDE0My4yOTEgMTg0LjQyNSAxNDMuMjQ0IDE4NC42MDggMTQzLjE1MUMxODQuNzkxIDE0My4wNTggMTg0Ljk0MyAxNDIuOTEzIDE4NS4wNjQgMTQyLjcxNkMxODUuMTkgMTQyLjUxNiAxODUuMjgzIDE0Mi4yNiAxODUuMzQ0IDE0MS45NDhDMTg1LjQwNSAxNDEuNjMzIDE4NS40MzUgMTQxLjI1NyAxODUuNDM1IDE0MC44MloiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuNTQiLz4KPC9nPgo8L2c+CjxyZWN0IHg9IjAuNSIgeT0iMC41IiB3aWR0aD0iMTk5IiBoZWlnaHQ9IjE1OSIgcng9IjMuNSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLW9wYWNpdHk9IjAuMTIiLz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZF80Njg1XzQwOTg5IiB4PSItOCIgeT0iLTQiIHdpZHRoPSIyMTYiIGhlaWdodD0iMTc2IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9IjQiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNCIvPgo8ZmVDb21wb3NpdGUgaW4yPSJoYXJkQWxwaGEiIG9wZXJhdG9yPSJvdXQiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd180Njg1XzQwOTg5Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93XzQ2ODVfNDA5ODkiIHJlc3VsdD0ic2hhcGUiLz4KPC9maWx0ZXI+CjxmaWx0ZXIgaWQ9ImZpbHRlcjFfYl80Njg1XzQwOTg5IiB4PSI2IiB5PSI2IiB3aWR0aD0iMTg4IiBoZWlnaHQ9IjE0OCIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPgo8ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPgo8ZmVHYXVzc2lhbkJsdXIgaW49IkJhY2tncm91bmRJbWFnZUZpeCIgc3RkRGV2aWF0aW9uPSIzIi8+CjxmZUNvbXBvc2l0ZSBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80Njg1XzQwOTg5Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9iYWNrZ3JvdW5kQmx1cl80Njg1XzQwOTg5IiByZXN1bHQ9InNoYXBlIi8+CjwvZmlsdGVyPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzQ2ODVfNDA5ODkiPgo8cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgcng9IjQiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==", "description": "Displays any value reading as a horizontal progress bar. Allows to configure value range, bar colors, and other settings.", "descriptor": { "type": "latest", @@ -19,7 +19,6 @@ "basicModeDirective": "tb-progress-bar-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"layout\":\"default\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":24,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"tickMin\":0,\"tickMax\":100,\"showTicks\":true,\"ticksFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"ticksColor\":\"rgba(0,0,0,0.54)\",\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Progress bar\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:water-percent\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}" }, - "externalId": null, "tags": [ "progress", "loading", diff --git a/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json b/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json index e19ff918be..a17d2ee031 100644 --- a/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json +++ b/application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json @@ -2,7 +2,7 @@ "fqn": "simple_value_and_chart_card", "name": "Simple Value and chart card", "deprecated": false, - "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAflBMVEUAAADg4ODg4ODf39/////g4OAhISHj4+M/Ut09PT2srKx0dHSQkJDn6fvHx8fx8fEvLy9XaOHP1PdYWFi3vvKenp7z9P3V1dW6urqCgoKHk+pvfeVLXd/b3/lmZmaTnuyrs/DDyfWfqO5jc+Orq6tLS0ufqe5LXN97iOhKSkrA29V9AAAABHRSTlMA77cggcfpsAAABRRJREFUeNrsz0EBACEIADBBedu/7RlDua3BBgAAAAAAwO/MjHpdzvOIvZ63Y45s8DiTHLVaiC6RErmMyG0+9stdRXIYiKLJ5RZ6IBUIITlx1A7m/39wq3A3y/ZqN5jA0/M4ILnqWhYc5MD+EXk1vqZIlTv4fPwhUnin4B1IHLie9YnEGFVtEryDwozLWYs4IZxOPUcAJVoxIT0LIFGmpY5dzyB2oOQ8feY+MWIFZrQxRxZPu2DNJSKiVB5AYgjkzkAdiAyqbEA9LAjiAQOyr8nYlNqsKUAj4EsLEpWeLLhCxGdBZkeyIeSwZkPkXtFYsHmqzUWK1JBgaT1frd8i3KRGi0QDVlwiMthEBpOJyBmJNZERmGZ0qIgcegZAHaVRnkV8n51FxPdYcYVIIZ22EPGCJ3eRrhp0LdLofKDIYBJnIVK4QYM4jyAB6V8nMsTAkitEoCpArM8iGyyZNm5AGadIZAbaQ6RbVMNDJPsTMrHiGpEbQwuczyJsB70JXmynSLVGlQOVemDQu4dIfbOF+oYL+Eukdziy7VsBZq5nVPNEZN92DyB9Tzdg5OGNhV7htifL7M7Mj3086xX/5fqPRj+Al+VbipRU8LJ8zf+Rz8wv9ukYBWIYBqJoO40KCePCjhBJYe/9T7ikCCHJBTTg32m6B/aCZGtBsrUg2VqQbOWD+L5bpYdIbDjrhRsS2KK426HoQgxpiOtRlalCCwkc9yGqlRRip+POEaSQrs97QCghjvIc6vxRQtrnTwwYIUTQ3lOdnRDSIJ9twOggf3bNqElVEAzDNwx+CpggUimW5s457f//g0eWU5gszTi7hRc+F/lmTuMz34eQZgvil2T1IkJeeuZfovySrFuk02ComZv+bEH8kqxBhAnRBsZD0zPVnrnubx6afW8MIraIumgY4Z0KL0UYh7odNxd9M/LgTWQRxqHphJBX8BZ/0i1F1BkMT5a6EkRUEca5sElwzuYfTd/1l0vXojADVzFFrq7n2d/GmzAW0MIwNVFCyp69TUSA9K+hrrEWIV1zKtFoMAzyTSI1D11DGR/QQgSHq2wZ688a9LlnTHQD8At7tYg/TXfQuZbXbPnX/eFg0LW4y9VuFgqIHI6E5jbuMpcNFcmQJafkmCVPuqFFU5pbO6nGOi2m7aUU6sHOqIigSJJiQ7lzmZhsOOJPTExISvwFRQGGWfuoGngnTG/o7jfvuHCoAyK7FBc0I+YcjUda2WwdcYUo3lnBE6XjaxX+iTRDDjCiG4Z+FdkFRDL7d4sSF4/ZsMc5ys2u0S23z9CLXXBF7sGEbBV6Db7ICX+aTYWxyaXJdMyWNCVpOm4LTOxoskI+vEZvxm+tJLGjIb1nMmZLQlIy7soxPtiDKd3/YFXxShE34sk8OzJbiDCcowh4IrYIySx74yhMBxK9k7AItb3j8iIRAVcUASPie9CH7Ivsn9WDMxQBX+Rjcu6Vy47DvUh5Pi+NaKBRKAK+yB7j4+SUKfLY3Y7I522nzsB7FANfZF9gMsseJS7shXk2WKSGc5xy+CKJWXdkhvwrZ//zA0mB0yrPysd6tQ00LYqFv0S5QRCd5Ef2qd1/nN5qAN2hWPgiObnxgQ6TPCP5OJWn46RQPf95V63hqa6EQaCFrFKESRSdtdzE3kQ2kbWyiayNTWRtbCJrYxNZG//auQMaAEAYiIFZwvxbZjLgd+egBirkNUEhdSJ0znInZoI0W6r+Xc2WCgAAAAAAYJsLnGVmvd/WiIIAAAAASUVORK5CYII=", + "image": "tb-image:c2ltcGxlX3ZhbHVlX2FuZF9jaGFydF9jYXJkX3N5c3RlbV93aWRnZXRfaW1hZ2UucG5n:IlNpbXBsZSBWYWx1ZSBhbmQgY2hhcnQgY2FyZCIgc3lzdGVtIHdpZGdldCBpbWFnZQ==;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDIwMCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHk9IjMyIiB3aWR0aD0iMjAwIiBoZWlnaHQ9Ijk2IiByeD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMC41IiB5PSIzMi41IiB3aWR0aD0iMTk5IiBoZWlnaHQ9Ijk1IiByeD0iMy41IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMC4xMiIvPgo8cGF0aCBkPSJNMTYuNzQxNyA0NS43NTc4VjU1SDE1LjE2MTFWNDUuNzU3OEgxNi43NDE3Wk0xOS42NDI2IDQ1Ljc1NzhWNDcuMDI3M0gxMi4yODU2VjQ1Ljc1NzhIMTkuNjQyNlpNMjMuMzc3NCA1NS4xMjdDMjIuODY5NiA1NS4xMjcgMjIuNDEwNSA1NS4wNDQ0IDIyIDU0Ljg3OTRDMjEuNTkzOCA1NC43MTAxIDIxLjI0NjcgNTQuNDc1MyAyMC45NTkgNTQuMTc0OEMyMC42NzU1IDUzLjg3NDMgMjAuNDU3NSA1My41MjEgMjAuMzA1MiA1My4xMTQ3QzIwLjE1MjggNTIuNzA4NSAyMC4wNzY3IDUyLjI3MDUgMjAuMDc2NyA1MS44MDA4VjUxLjU0NjlDMjAuMDc2NyA1MS4wMDk0IDIwLjE1NDkgNTAuNTIyOCAyMC4zMTE1IDUwLjA4NjlDMjAuNDY4MSA0OS42NTEgMjAuNjg2IDQ5LjI3ODYgMjAuOTY1MyA0OC45Njk3QzIxLjI0NDYgNDguNjU2NiAyMS41NzQ3IDQ4LjQxNzUgMjEuOTU1NiA0OC4yNTI0QzIyLjMzNjQgNDguMDg3NCAyMi43NDkgNDguMDA0OSAyMy4xOTM0IDQ4LjAwNDlDMjMuNjg0MiA0OC4wMDQ5IDI0LjExMzggNDguMDg3NCAyNC40ODE5IDQ4LjI1MjRDMjQuODUwMSA0OC40MTc1IDI1LjE1NDggNDguNjUwMiAyNS4zOTYgNDguOTUwN0MyNS42NDE0IDQ5LjI0NjkgMjUuODIzNCA0OS42MDAzIDI1Ljk0MTkgNTAuMDEwN0MyNi4wNjQ2IDUwLjQyMTIgMjYuMTI2IDUwLjg3NCAyNi4xMjYgNTEuMzY5MVY1Mi4wMjI5SDIwLjgxOTNWNTAuOTI0OEgyNC42MTUyVjUwLjgwNDJDMjQuNjA2OCA1MC41MjkxIDI0LjU1MTggNTAuMjcxIDI0LjQ1MDIgNTAuMDI5OEMyNC4zNTI5IDQ5Ljc4ODYgMjQuMjAyNiA0OS41OTM5IDIzLjk5OTUgNDkuNDQ1OEMyMy43OTY0IDQ5LjI5NzcgMjMuNTI1NiA0OS4yMjM2IDIzLjE4NyA0OS4yMjM2QzIyLjkzMzEgNDkuMjIzNiAyMi43MDY3IDQ5LjI3ODYgMjIuNTA3OCA0OS4zODg3QzIyLjMxMzIgNDkuNDk0NSAyMi4xNTAyIDQ5LjY0ODkgMjIuMDE5IDQ5Ljg1MjFDMjEuODg3OSA1MC4wNTUyIDIxLjc4NjMgNTAuMzAwNiAyMS43MTQ0IDUwLjU4ODRDMjEuNjQ2NiA1MC44NzE5IDIxLjYxMjggNTEuMTkxNCAyMS42MTI4IDUxLjU0NjlWNTEuODAwOEMyMS42MTI4IDUyLjEwMTIgMjEuNjUzIDUyLjM4MDUgMjEuNzMzNCA1Mi42Mzg3QzIxLjgxOCA1Mi44OTI2IDIxLjk0MDggNTMuMTE0NyAyMi4xMDE2IDUzLjMwNTJDMjIuMjYyNCA1My40OTU2IDIyLjQ1NyA1My42NDU4IDIyLjY4NTUgNTMuNzU1OUMyMi45MTQxIDUzLjg2MTcgMjMuMTc0MyA1My45MTQ2IDIzLjQ2NjMgNTMuOTE0NkMyMy44MzQ1IDUzLjkxNDYgMjQuMTYyNCA1My44NDA1IDI0LjQ1MDIgNTMuNjkyNEMyNC43MzggNTMuNTQ0MyAyNC45ODc2IDUzLjMzNDggMjUuMTk5MiA1My4wNjRMMjYuMDA1NCA1My44NDQ3QzI1Ljg1NzMgNTQuMDYwNSAyNS42NjQ3IDU0LjI2NzkgMjUuNDI3NyA1NC40NjY4QzI1LjE5MDggNTQuNjYxNSAyNC45MDA5IDU0LjgyMDEgMjQuNTU4MSA1NC45NDI5QzI0LjIxOTYgNTUuMDY1NiAyMy44MjYgNTUuMTI3IDIzLjM3NzQgNTUuMTI3Wk0yOS4wOTkxIDQ5LjUyODNWNTVIMjcuNTY5M1Y0OC4xMzE4SDI5LjAxMDNMMjkuMDk5MSA0OS41MjgzWk0yOC44NTE2IDUxLjMxMkwyOC4zMzExIDUxLjMwNTdDMjguMzMxMSA1MC44MzE3IDI4LjM5MDMgNTAuMzkzNyAyOC41MDg4IDQ5Ljk5MTdDMjguNjI3MyA0OS41ODk3IDI4LjgwMDggNDkuMjQwNiAyOS4wMjkzIDQ4Ljk0NDNDMjkuMjU3OCA0OC42NDM5IDI5LjU0MTMgNDguNDEzMiAyOS44Nzk5IDQ4LjI1MjRDMzAuMjIyNyA0OC4wODc0IDMwLjYxODMgNDguMDA0OSAzMS4wNjY5IDQ4LjAwNDlDMzEuMzggNDguMDA0OSAzMS42NjU3IDQ4LjA1MTQgMzEuOTIzOCA0OC4xNDQ1QzMyLjE4NjIgNDguMjMzNCAzMi40MTI2IDQ4LjM3NTIgMzIuNjAzIDQ4LjU2OThDMzIuNzk3NyA0OC43NjQ1IDMyLjk0NTggNDkuMDE0MiAzMy4wNDc0IDQ5LjMxODhDMzMuMTUzMiA0OS42MjM1IDMzLjIwNjEgNDkuOTkxNyAzMy4yMDYxIDUwLjQyMzNWNTVIMzEuNjc2M1Y1MC41NTY2QzMxLjY3NjMgNTAuMjIyMyAzMS42MjU1IDQ5Ljk2IDMxLjUyMzkgNDkuNzY5NUMzMS40MjY2IDQ5LjU3OTEgMzEuMjg0OCA0OS40NDM3IDMxLjA5ODYgNDkuMzYzM0MzMC45MTY3IDQ5LjI3ODYgMzAuNjk4NyA0OS4yMzYzIDMwLjQ0NDggNDkuMjM2M0MzMC4xNTcxIDQ5LjIzNjMgMjkuOTExNiA0OS4yOTEzIDI5LjcwODUgNDkuNDAxNEMyOS41MDk2IDQ5LjUxMTQgMjkuMzQ2NyA0OS42NjE2IDI5LjIxOTcgNDkuODUyMUMyOS4wOTI4IDUwLjA0MjUgMjguOTk5NyA1MC4yNjI1IDI4Ljk0MDQgNTAuNTEyMkMyOC44ODEyIDUwLjc2MTkgMjguODUxNiA1MS4wMjg1IDI4Ljg1MTYgNTEuMzEyWk0zMy4xMTA4IDUwLjkwNThMMzIuMzkzNiA1MS4wNjQ1QzMyLjM5MzYgNTAuNjQ5NyAzMi40NTA3IDUwLjI1ODMgMzIuNTY0OSA0OS44OTAxQzMyLjY4MzQgNDkuNTE3NyAzMi44NTQ4IDQ5LjE5MTkgMzMuMDc5MSA0OC45MTI2QzMzLjMwNzYgNDguNjI5MSAzMy41ODkgNDguNDA2OSAzMy45MjMzIDQ4LjI0NjFDMzQuMjU3NiA0OC4wODUzIDM0LjY0MDYgNDguMDA0OSAzNS4wNzIzIDQ4LjAwNDlDMzUuNDIzNSA0OC4wMDQ5IDM1LjczNjcgNDguMDUzNSAzNi4wMTE3IDQ4LjE1MDlDMzYuMjkxIDQ4LjI0NCAzNi41MjggNDguMzkyMSAzNi43MjI3IDQ4LjU5NTJDMzYuOTE3MyA0OC43OTgzIDM3LjA2NTQgNDkuMDYyOCAzNy4xNjcgNDkuMzg4N0MzNy4yNjg2IDQ5LjcxMDMgMzcuMzE5MyA1MC4wOTk2IDM3LjMxOTMgNTAuNTU2NlY1NUgzNS43ODMyVjUwLjU1MDNDMzUuNzgzMiA1MC4yMDMzIDM1LjczMjQgNDkuOTM0NiAzNS42MzA5IDQ5Ljc0NDFDMzUuNTMzNSA0OS41NTM3IDM1LjM5MzkgNDkuNDIyNSAzNS4yMTE5IDQ5LjM1MDZDMzUuMDI5OSA0OS4yNzQ0IDM0LjgxMiA0OS4yMzYzIDM0LjU1ODEgNDkuMjM2M0MzNC4zMjExIDQ5LjIzNjMgMzQuMTExNyA0OS4yODA4IDMzLjkyOTcgNDkuMzY5NkMzMy43NTIgNDkuNDU0MyAzMy42MDE3IDQ5LjU3NDkgMzMuNDc5IDQ5LjczMTRDMzMuMzU2MyA0OS44ODM4IDMzLjI2MzIgNTAuMDU5NCAzMy4xOTk3IDUwLjI1ODNDMzMuMTQwNSA1MC40NTcyIDMzLjExMDggNTAuNjczIDMzLjExMDggNTAuOTA1OFpNNDAuNjc5NyA0OS40NTIxVjU3LjY0MDZIMzkuMTQ5OVY0OC4xMzE4SDQwLjU1OTFMNDAuNjc5NyA0OS40NTIxWk00NS4xNTQ4IDUxLjUwMjRWNTEuNjM1N0M0NS4xNTQ4IDUyLjEzNTEgNDUuMDk1NSA1Mi41OTg1IDQ0Ljk3NzEgNTMuMDI1OUM0NC44NjI4IDUzLjQ0OTEgNDQuNjkxNCA1My44MTkzIDQ0LjQ2MjkgNTQuMTM2N0M0NC4yMzg2IDU0LjQ0OTkgNDMuOTYxNCA1NC42OTMyIDQzLjYzMTMgNTQuODY2N0M0My4zMDEzIDU1LjA0MDIgNDIuOTIwNCA1NS4xMjcgNDIuNDg4OCA1NS4xMjdDNDIuMDYxNCA1NS4xMjcgNDEuNjg2OCA1NS4wNDg3IDQxLjM2NTIgNTQuODkyMUM0MS4wNDc5IDU0LjczMTMgNDAuNzc5MSA1NC41MDQ5IDQwLjU1OTEgNTQuMjEyOUM0MC4zMzkgNTMuOTIwOSA0MC4xNjEzIDUzLjU3ODEgNDAuMDI1OSA1My4xODQ2QzM5Ljg5NDcgNTIuNzg2OCAzOS44MDE2IDUyLjM1MDkgMzkuNzQ2NiA1MS44NzdWNTEuMzYyOEMzOS44MDE2IDUwLjg1OTIgMzkuODk0NyA1MC40MDIyIDQwLjAyNTkgNDkuOTkxN0M0MC4xNjEzIDQ5LjU4MTIgNDAuMzM5IDQ5LjIyNzkgNDAuNTU5MSA0OC45MzE2QzQwLjc3OTEgNDguNjM1NCA0MS4wNDc5IDQ4LjQwNjkgNDEuMzY1MiA0OC4yNDYxQzQxLjY4MjYgNDguMDg1MyA0Mi4wNTI5IDQ4LjAwNDkgNDIuNDc2MSA0OC4wMDQ5QzQyLjkwNzcgNDguMDA0OSA0My4yOTA3IDQ4LjA4OTUgNDMuNjI1IDQ4LjI1ODhDNDMuOTU5MyA0OC40MjM4IDQ0LjI0MDcgNDguNjYwOCA0NC40NjkyIDQ4Ljk2OTdDNDQuNjk3OCA0OS4yNzQ0IDQ0Ljg2OTEgNDkuNjQyNiA0NC45ODM0IDUwLjA3NDJDNDUuMDk3NyA1MC41MDE2IDQ1LjE1NDggNTAuOTc3NyA0NS4xNTQ4IDUxLjUwMjRaTTQzLjYyNSA1MS42MzU3VjUxLjUwMjRDNDMuNjI1IDUxLjE4NTEgNDMuNTk1NCA1MC44OTEgNDMuNTM2MSA1MC42MjAxQzQzLjQ3NjkgNTAuMzQ1MSA0My4zODM4IDUwLjEwMzggNDMuMjU2OCA0OS44OTY1QzQzLjEyOTkgNDkuNjg5MSA0Mi45NjcgNDkuNTI4MyA0Mi43NjgxIDQ5LjQxNDFDNDIuNTczNCA0OS4yOTU2IDQyLjMzODUgNDkuMjM2MyA0Mi4wNjM1IDQ5LjIzNjNDNDEuNzkyNiA0OS4yMzYzIDQxLjU1OTkgNDkuMjgyOSA0MS4zNjUyIDQ5LjM3NkM0MS4xNzA2IDQ5LjQ2NDggNDEuMDA3NiA0OS41ODk3IDQwLjg3NjUgNDkuNzUwNUM0MC43NDUzIDQ5LjkxMTMgNDAuNjQzNyA1MC4wOTk2IDQwLjU3MTggNTAuMzE1NEM0MC40OTk4IDUwLjUyNyA0MC40NDkxIDUwLjc1NzYgNDAuNDE5NCA1MS4wMDczVjUyLjIzODhDNDAuNDcwMiA1Mi41NDM1IDQwLjU1NyA1Mi44MjI4IDQwLjY3OTcgNTMuMDc2N0M0MC44MDI0IDUzLjMzMDYgNDAuOTc1OSA1My41MzM3IDQxLjIwMDIgNTMuNjg2QzQxLjQyODcgNTMuODM0MSA0MS43MjA3IDUzLjkwODIgNDIuMDc2MiA1My45MDgyQzQyLjM1MTIgNTMuOTA4MiA0Mi41ODYxIDUzLjg0OSA0Mi43ODA4IDUzLjczMDVDNDIuOTc1NCA1My42MTIgNDMuMTM0MSA1My40NDkxIDQzLjI1NjggNTMuMjQxN0M0My4zODM4IDUzLjAzMDEgNDMuNDc2OSA1Mi43ODY4IDQzLjUzNjEgNTIuNTExN0M0My41OTU0IDUyLjIzNjcgNDMuNjI1IDUxLjk0NDcgNDMuNjI1IDUxLjYzNTdaTTQ5LjczMzkgNTUuMTI3QzQ5LjIyNjEgNTUuMTI3IDQ4Ljc2NjkgNTUuMDQ0NCA0OC4zNTY0IDU0Ljg3OTRDNDcuOTUwMiA1NC43MTAxIDQ3LjYwMzIgNTQuNDc1MyA0Ny4zMTU0IDU0LjE3NDhDNDcuMDMxOSA1My44NzQzIDQ2LjgxNCA1My41MjEgNDYuNjYxNiA1My4xMTQ3QzQ2LjUwOTMgNTIuNzA4NSA0Ni40MzMxIDUyLjI3MDUgNDYuNDMzMSA1MS44MDA4VjUxLjU0NjlDNDYuNDMzMSA1MS4wMDk0IDQ2LjUxMTQgNTAuNTIyOCA0Ni42NjggNTAuMDg2OUM0Ni44MjQ1IDQ5LjY1MSA0Ny4wNDI1IDQ5LjI3ODYgNDcuMzIxOCA0OC45Njk3QzQ3LjYwMTEgNDguNjU2NiA0Ny45MzEyIDQ4LjQxNzUgNDguMzEyIDQ4LjI1MjRDNDguNjkyOSA0OC4wODc0IDQ5LjEwNTUgNDguMDA0OSA0OS41NDk4IDQ4LjAwNDlDNTAuMDQwNyA0OC4wMDQ5IDUwLjQ3MDIgNDguMDg3NCA1MC44Mzg0IDQ4LjI1MjRDNTEuMjA2NSA0OC40MTc1IDUxLjUxMTIgNDguNjUwMiA1MS43NTI0IDQ4Ljk1MDdDNTEuOTk3OSA0OS4yNDY5IDUyLjE3OTkgNDkuNjAwMyA1Mi4yOTgzIDUwLjAxMDdDNTIuNDIxMSA1MC40MjEyIDUyLjQ4MjQgNTAuODc0IDUyLjQ4MjQgNTEuMzY5MVY1Mi4wMjI5SDQ3LjE3NThWNTAuOTI0OEg1MC45NzE3VjUwLjgwNDJDNTAuOTYzMiA1MC41MjkxIDUwLjkwODIgNTAuMjcxIDUwLjgwNjYgNTAuMDI5OEM1MC43MDkzIDQ5Ljc4ODYgNTAuNTU5MSA0OS41OTM5IDUwLjM1NiA0OS40NDU4QzUwLjE1MjggNDkuMjk3NyA0OS44ODIgNDkuMjIzNiA0OS41NDM1IDQ5LjIyMzZDNDkuMjg5NiA0OS4yMjM2IDQ5LjA2MzIgNDkuMjc4NiA0OC44NjQzIDQ5LjM4ODdDNDguNjY5NiA0OS40OTQ1IDQ4LjUwNjcgNDkuNjQ4OSA0OC4zNzU1IDQ5Ljg1MjFDNDguMjQ0MyA1MC4wNTUyIDQ4LjE0MjcgNTAuMzAwNiA0OC4wNzA4IDUwLjU4ODRDNDguMDAzMSA1MC44NzE5IDQ3Ljk2OTIgNTEuMTkxNCA0Ny45NjkyIDUxLjU0NjlWNTEuODAwOEM0Ny45NjkyIDUyLjEwMTIgNDguMDA5NCA1Mi4zODA1IDQ4LjA4OTggNTIuNjM4N0M0OC4xNzQ1IDUyLjg5MjYgNDguMjk3MiA1My4xMTQ3IDQ4LjQ1OCA1My4zMDUyQzQ4LjYxODggNTMuNDk1NiA0OC44MTM1IDUzLjY0NTggNDkuMDQyIDUzLjc1NTlDNDkuMjcwNSA1My44NjE3IDQ5LjUzMDggNTMuOTE0NiA0OS44MjI4IDUzLjkxNDZDNTAuMTkwOSA1My45MTQ2IDUwLjUxODkgNTMuODQwNSA1MC44MDY2IDUzLjY5MjRDNTEuMDk0NCA1My41NDQzIDUxLjM0NDEgNTMuMzM0OCA1MS41NTU3IDUzLjA2NEw1Mi4zNjE4IDUzLjg0NDdDNTIuMjEzNyA1NC4wNjA1IDUyLjAyMTIgNTQuMjY3OSA1MS43ODQyIDU0LjQ2NjhDNTEuNTQ3MiA1NC42NjE1IDUxLjI1NzMgNTQuODIwMSA1MC45MTQ2IDU0Ljk0MjlDNTAuNTc2IDU1LjA2NTYgNTAuMTgyNSA1NS4xMjcgNDkuNzMzOSA1NS4xMjdaTTU1LjQ2MTkgNDkuNDM5NVY1NUg1My45MzIxVjQ4LjEzMThINTUuMzkyMUw1NS40NjE5IDQ5LjQzOTVaTTU3LjU2MyA0OC4wODc0TDU3LjU1MDMgNDkuNTA5M0M1Ny40NTcyIDQ5LjQ5MjQgNTcuMzU1NiA0OS40Nzk3IDU3LjI0NTYgNDkuNDcxMkM1Ny4xMzk4IDQ5LjQ2MjcgNTcuMDM0IDQ5LjQ1ODUgNTYuOTI4MiA0OS40NTg1QzU2LjY2NTkgNDkuNDU4NSA1Ni40MzUyIDQ5LjQ5NjYgNTYuMjM2MyA0OS41NzI4QzU2LjAzNzQgNDkuNjQ0NyA1NS44NzAzIDQ5Ljc1MDUgNTUuNzM0OSA0OS44OTAxQzU1LjYwMzcgNTAuMDI1NiA1NS41MDIxIDUwLjE5MDYgNTUuNDMwMiA1MC4zODUzQzU1LjM1ODIgNTAuNTc5OSA1NS4zMTU5IDUwLjc5NzkgNTUuMzAzMiA1MS4wMzkxTDU0Ljk1NDEgNTEuMDY0NUM1NC45NTQxIDUwLjYzMjggNTQuOTk2NCA1MC4yMzI5IDU1LjA4MTEgNDkuODY0N0M1NS4xNjU3IDQ5LjQ5NjYgNTUuMjkyNiA0OS4xNzI5IDU1LjQ2MTkgNDguODkzNkM1NS42MzU0IDQ4LjYxNDMgNTUuODUxMiA0OC4zOTYzIDU2LjEwOTQgNDguMjM5N0M1Ni4zNzE3IDQ4LjA4MzIgNTYuNjc0MyA0OC4wMDQ5IDU3LjAxNzEgNDguMDA0OUM1Ny4xMTAyIDQ4LjAwNDkgNTcuMjA5NiA0OC4wMTMzIDU3LjMxNTQgNDguMDMwM0M1Ny40MjU1IDQ4LjA0NzIgNTcuNTA4IDQ4LjA2NjIgNTcuNTYzIDQ4LjA4NzRaTTYyLjQxNSA1My42MjI2VjUwLjM0NzJDNjIuNDE1IDUwLjEwMTcgNjIuMzcwNiA0OS44OTAxIDYyLjI4MTcgNDkuNzEyNEM2Mi4xOTI5IDQ5LjUzNDcgNjIuMDU3NSA0OS4zOTcxIDYxLjg3NTUgNDkuMjk5OEM2MS42OTc4IDQ5LjIwMjUgNjEuNDczNSA0OS4xNTM4IDYxLjIwMjYgNDkuMTUzOEM2MC45NTMgNDkuMTUzOCA2MC43MzcxIDQ5LjE5NjEgNjAuNTU1MiA0OS4yODA4QzYwLjM3MzIgNDkuMzY1NCA2MC4yMzE0IDQ5LjQ3OTcgNjAuMTI5OSA0OS42MjM1QzYwLjAyODMgNDkuNzY3NCA1OS45Nzc1IDQ5LjkzMDMgNTkuOTc3NSA1MC4xMTIzSDU4LjQ1NDFDNTguNDU0MSA0OS44NDE1IDU4LjUxOTcgNDkuNTc5MSA1OC42NTA5IDQ5LjMyNTJDNTguNzgyMSA0OS4wNzEzIDU4Ljk3MjUgNDguODQ0OSA1OS4yMjIyIDQ4LjY0NkM1OS40NzE4IDQ4LjQ0NzEgNTkuNzcwMiA0OC4yOTA1IDYwLjExNzIgNDguMTc2M0M2MC40NjQyIDQ4LjA2MiA2MC44NTM1IDQ4LjAwNDkgNjEuMjg1MiA0OC4wMDQ5QzYxLjgwMTQgNDguMDA0OSA2Mi4yNTg1IDQ4LjA5MTYgNjIuNjU2MiA0OC4yNjUxQzYzLjA1ODMgNDguNDM4NiA2My4zNzM1IDQ4LjcwMSA2My42MDIxIDQ5LjA1MjJDNjMuODM0OCA0OS4zOTkzIDYzLjk1MTIgNDkuODM1MSA2My45NTEyIDUwLjM1OTlWNTMuNDEzMUM2My45NTEyIDUzLjcyNjIgNjMuOTcyMyA1NC4wMDc2IDY0LjAxNDYgNTQuMjU3M0M2NC4wNjEyIDU0LjUwMjggNjQuMTI2OCA1NC43MTY1IDY0LjIxMTQgNTQuODk4NFY1NUg2Mi42NDM2QzYyLjU3MTYgNTQuODM1IDYyLjUxNDUgNTQuNjI1NSA2Mi40NzIyIDU0LjM3MTZDNjIuNDM0MSA1NC4xMTM0IDYyLjQxNSA1My44NjM4IDYyLjQxNSA1My42MjI2Wk02Mi42MzcyIDUwLjgyMzJMNjIuNjQ5OSA1MS43NjlINjEuNTUxOEM2MS4yNjgyIDUxLjc2OSA2MS4wMTg2IDUxLjc5NjUgNjAuODAyNyA1MS44NTE2QzYwLjU4NjkgNTEuOTAyMyA2MC40MDcxIDUxLjk3ODUgNjAuMjYzMiA1Mi4wODAxQzYwLjExOTMgNTIuMTgxNiA2MC4wMTE0IDUyLjMwNDQgNTkuOTM5NSA1Mi40NDgyQzU5Ljg2NzUgNTIuNTkyMSA1OS44MzE1IDUyLjc1NSA1OS44MzE1IDUyLjkzN0M1OS44MzE1IDUzLjExOSA1OS44NzM5IDUzLjI4NjEgNTkuOTU4NSA1My40Mzg1QzYwLjA0MzEgNTMuNTg2NiA2MC4xNjU5IDUzLjcwMyA2MC4zMjY3IDUzLjc4NzZDNjAuNDkxNyA1My44NzIyIDYwLjY5MDYgNTMuOTE0NiA2MC45MjMzIDUzLjkxNDZDNjEuMjM2NSA1My45MTQ2IDYxLjUwOTQgNTMuODUxMSA2MS43NDIyIDUzLjcyNDFDNjEuOTc5MiA1My41OTI5IDYyLjE2NTQgNTMuNDM0MiA2Mi4zMDA4IDUzLjI0OEM2Mi40MzYyIDUzLjA1NzYgNjIuNTA4MSA1Mi44Nzc4IDYyLjUxNjYgNTIuNzA4NUw2My4wMTE3IDUzLjM4NzdDNjIuOTYwOSA1My41NjEyIDYyLjg3NDIgNTMuNzQ3NCA2Mi43NTE1IDUzLjk0NjNDNjIuNjI4NyA1NC4xNDUyIDYyLjQ2NzkgNTQuMzM1NiA2Mi4yNjkgNTQuNTE3NkM2Mi4wNzQ0IDU0LjY5NTMgNjEuODM5NSA1NC44NDEzIDYxLjU2NDUgNTQuOTU1NkM2MS4yOTM2IDU1LjA2OTggNjAuOTgwNSA1NS4xMjcgNjAuNjI1IDU1LjEyN0M2MC4xNzY0IDU1LjEyNyA1OS43NzY1IDU1LjAzODEgNTkuNDI1MyA1NC44NjA0QzU5LjA3NDEgNTQuNjc4NCA1OC43OTkgNTQuNDM1MSA1OC42MDAxIDU0LjEzMDRDNTguNDAxMiA1My44MjE1IDU4LjMwMTggNTMuNDcyMyA1OC4zMDE4IDUzLjA4M0M1OC4zMDE4IDUyLjcxOTEgNTguMzY5NSA1Mi4zOTc1IDU4LjUwNDkgNTIuMTE4MkM1OC42NDQ1IDUxLjgzNDYgNTguODQ3NyA1MS41OTc3IDU5LjExNDMgNTEuNDA3MkM1OS4zODUxIDUxLjIxNjggNTkuNzE1MiA1MS4wNzI5IDYwLjEwNDUgNTAuOTc1NkM2MC40OTM4IDUwLjg3NCA2MC45MzgyIDUwLjgyMzIgNjEuNDM3NSA1MC44MjMySDYyLjYzNzJaTTY4Ljk0OTIgNDguMTMxOFY0OS4yNDlINjUuMDc3MVY0OC4xMzE4SDY4Ljk0OTJaTTY2LjE5NDMgNDYuNDQ5N0g2Ny43MjQxVjUzLjEwMjFDNjcuNzI0MSA1My4zMTM2IDY3Ljc1MzcgNTMuNDc2NiA2Ny44MTMgNTMuNTkwOEM2Ny44NzY1IDUzLjcwMDggNjcuOTYzMiA1My43NzQ5IDY4LjA3MzIgNTMuODEzQzY4LjE4MzMgNTMuODUxMSA2OC4zMTIzIDUzLjg3MDEgNjguNDYwNCA1My44NzAxQzY4LjU2NjIgNTMuODcwMSA2OC42Njc4IDUzLjg2MzggNjguNzY1MSA1My44NTExQzY4Ljg2MjUgNTMuODM4NCA2OC45NDA4IDUzLjgyNTcgNjkgNTMuODEzTDY5LjAwNjMgNTQuOTgxQzY4Ljg3OTQgNTUuMDE5IDY4LjczMTMgNTUuMDUyOSA2OC41NjIgNTUuMDgyNUM2OC4zOTcgNTUuMTEyMSA2OC4yMDY1IDU1LjEyNyA2Ny45OTA3IDU1LjEyN0M2Ny42Mzk1IDU1LjEyNyA2Ny4zMjg1IDU1LjA2NTYgNjcuMDU3NiA1NC45NDI5QzY2Ljc4NjggNTQuODE1OSA2Ni41NzUyIDU0LjYxMDcgNjYuNDIyOSA1NC4zMjcxQzY2LjI3MDUgNTQuMDQzNiA2Ni4xOTQzIDUzLjY2NyA2Ni4xOTQzIDUzLjE5NzNWNDYuNDQ5N1pNNzQuNTEyMiA1My4zODEzVjQ4LjEzMThINzYuMDQ4M1Y1NUg3NC42MDExTDc0LjUxMjIgNTMuMzgxM1pNNzQuNzI4IDUxLjk1MzFMNzUuMjQyMiA1MS45NDA0Qzc1LjI0MjIgNTIuNDAxNyA3NS4xOTE0IDUyLjgyNyA3NS4wODk4IDUzLjIxNjNDNzQuOTg4MyA1My42MDE0IDc0LjgzMTcgNTMuOTM3OCA3NC42MjAxIDU0LjIyNTZDNzQuNDA4NSA1NC41MDkxIDc0LjEzNzcgNTQuNzMxMyA3My44MDc2IDU0Ljg5MjFDNzMuNDc3NSA1NS4wNDg3IDczLjA4MTkgNTUuMTI3IDcyLjYyMDYgNTUuMTI3QzcyLjI4NjMgNTUuMTI3IDcxLjk3OTUgNTUuMDc4MyA3MS43MDAyIDU0Ljk4MUM3MS40MjA5IDU0Ljg4MzYgNzEuMTc5NyA1NC43MzM0IDcwLjk3NjYgNTQuNTMwM0M3MC43Nzc3IDU0LjMyNzEgNzAuNjIzMiA1NC4wNjI3IDcwLjUxMzIgNTMuNzM2OEM3MC40MDMyIDUzLjQxMSA3MC4zNDgxIDUzLjAyMTYgNzAuMzQ4MSA1Mi41Njg4VjQ4LjEzMThINzEuODc3OVY1Mi41ODE1QzcxLjg3NzkgNTIuODMxMiA3MS45MDc2IDUzLjA0MDcgNzEuOTY2OCA1My4yMUM3Mi4wMjYgNTMuMzc1IDcyLjEwNjQgNTMuNTA4MyA3Mi4yMDggNTMuNjA5OUM3Mi4zMDk2IDUzLjcxMTQgNzIuNDI4MSA1My43ODM0IDcyLjU2MzUgNTMuODI1N0M3Mi42OTg5IDUzLjg2OCA3Mi44NDI4IDUzLjg4OTIgNzIuOTk1MSA1My44ODkyQzczLjQzMSA1My44ODkyIDczLjc3MzggNTMuODA0NSA3NC4wMjM0IDUzLjYzNTNDNzQuMjc3MyA1My40NjE4IDc0LjQ1NzIgNTMuMjI5IDc0LjU2MyA1Mi45MzdDNzQuNjczIDUyLjY0NSA3NC43MjggNTIuMzE3MSA3NC43MjggNTEuOTUzMVpNNzkuNDAyMyA0OS40Mzk1VjU1SDc3Ljg3MjZWNDguMTMxOEg3OS4zMzI1TDc5LjQwMjMgNDkuNDM5NVpNODEuNTAzNCA0OC4wODc0TDgxLjQ5MDcgNDkuNTA5M0M4MS4zOTc2IDQ5LjQ5MjQgODEuMjk2MSA0OS40Nzk3IDgxLjE4NiA0OS40NzEyQzgxLjA4MDIgNDkuNDYyNyA4MC45NzQ0IDQ5LjQ1ODUgODAuODY4NyA0OS40NTg1QzgwLjYwNjMgNDkuNDU4NSA4MC4zNzU3IDQ5LjQ5NjYgODAuMTc2OCA0OS41NzI4Qzc5Ljk3NzkgNDkuNjQ0NyA3OS44MTA3IDQ5Ljc1MDUgNzkuNjc1MyA0OS44OTAxQzc5LjU0NDEgNTAuMDI1NiA3OS40NDI1IDUwLjE5MDYgNzkuMzcwNiA1MC4zODUzQzc5LjI5ODcgNTAuNTc5OSA3OS4yNTYzIDUwLjc5NzkgNzkuMjQzNyA1MS4wMzkxTDc4Ljg5NDUgNTEuMDY0NUM3OC44OTQ1IDUwLjYzMjggNzguOTM2OCA1MC4yMzI5IDc5LjAyMTUgNDkuODY0N0M3OS4xMDYxIDQ5LjQ5NjYgNzkuMjMzMSA0OS4xNzI5IDc5LjQwMjMgNDguODkzNkM3OS41NzU4IDQ4LjYxNDMgNzkuNzkxNyA0OC4zOTYzIDgwLjA0OTggNDguMjM5N0M4MC4zMTIyIDQ4LjA4MzIgODAuNjE0NyA0OC4wMDQ5IDgwLjk1NzUgNDguMDA0OUM4MS4wNTA2IDQ4LjAwNDkgODEuMTUwMSA0OC4wMTMzIDgxLjI1NTkgNDguMDMwM0M4MS4zNjU5IDQ4LjA0NzIgODEuNDQ4NCA0OC4wNjYyIDgxLjUwMzQgNDguMDg3NFpNODUuNjEyOCA1NS4xMjdDODUuMTA1IDU1LjEyNyA4NC42NDU4IDU1LjA0NDQgODQuMjM1NCA1NC44Nzk0QzgzLjgyOTEgNTQuNzEwMSA4My40ODIxIDU0LjQ3NTMgODMuMTk0MyA1NC4xNzQ4QzgyLjkxMDggNTMuODc0MyA4Mi42OTI5IDUzLjUyMSA4Mi41NDA1IDUzLjExNDdDODIuMzg4MiA1Mi43MDg1IDgyLjMxMiA1Mi4yNzA1IDgyLjMxMiA1MS44MDA4VjUxLjU0NjlDODIuMzEyIDUxLjAwOTQgODIuMzkwMyA1MC41MjI4IDgyLjU0NjkgNTAuMDg2OUM4Mi43MDM1IDQ5LjY1MSA4Mi45MjE0IDQ5LjI3ODYgODMuMjAwNyA0OC45Njk3QzgzLjQ4IDQ4LjY1NjYgODMuODEwMSA0OC40MTc1IDg0LjE5MDkgNDguMjUyNEM4NC41NzE4IDQ4LjA4NzQgODQuOTg0NCA0OC4wMDQ5IDg1LjQyODcgNDguMDA0OUM4NS45MTk2IDQ4LjAwNDkgODYuMzQ5MSA0OC4wODc0IDg2LjcxNzMgNDguMjUyNEM4Ny4wODU0IDQ4LjQxNzUgODcuMzkwMSA0OC42NTAyIDg3LjYzMTMgNDguOTUwN0M4Ny44NzY4IDQ5LjI0NjkgODguMDU4OCA0OS42MDAzIDg4LjE3NzIgNTAuMDEwN0M4OC4zIDUwLjQyMTIgODguMzYxMyA1MC44NzQgODguMzYxMyA1MS4zNjkxVjUyLjAyMjlIODMuMDU0N1Y1MC45MjQ4SDg2Ljg1MDZWNTAuODA0MkM4Ni44NDIxIDUwLjUyOTEgODYuNzg3MSA1MC4yNzEgODYuNjg1NSA1MC4wMjk4Qzg2LjU4ODIgNDkuNzg4NiA4Ni40MzggNDkuNTkzOSA4Ni4yMzQ5IDQ5LjQ0NThDODYuMDMxNyA0OS4yOTc3IDg1Ljc2MDkgNDkuMjIzNiA4NS40MjI0IDQ5LjIyMzZDODUuMTY4NSA0OS4yMjM2IDg0Ljk0MjEgNDkuMjc4NiA4NC43NDMyIDQ5LjM4ODdDODQuNTQ4NSA0OS40OTQ1IDg0LjM4NTYgNDkuNjQ4OSA4NC4yNTQ0IDQ5Ljg1MjFDODQuMTIzMiA1MC4wNTUyIDg0LjAyMTYgNTAuMzAwNiA4My45NDk3IDUwLjU4ODRDODMuODgyIDUwLjg3MTkgODMuODQ4MSA1MS4xOTE0IDgzLjg0ODEgNTEuNTQ2OVY1MS44MDA4QzgzLjg0ODEgNTIuMTAxMiA4My44ODgzIDUyLjM4MDUgODMuOTY4OCA1Mi42Mzg3Qzg0LjA1MzQgNTIuODkyNiA4NC4xNzYxIDUzLjExNDcgODQuMzM2OSA1My4zMDUyQzg0LjQ5NzcgNTMuNDk1NiA4NC42OTI0IDUzLjY0NTggODQuOTIwOSA1My43NTU5Qzg1LjE0OTQgNTMuODYxNyA4NS40MDk3IDUzLjkxNDYgODUuNzAxNyA1My45MTQ2Qzg2LjA2OTggNTMuOTE0NiA4Ni4zOTc4IDUzLjg0MDUgODYuNjg1NSA1My42OTI0Qzg2Ljk3MzMgNTMuNTQ0MyA4Ny4yMjMgNTMuMzM0OCA4Ny40MzQ2IDUzLjA2NEw4OC4yNDA3IDUzLjg0NDdDODguMDkyNiA1NC4wNjA1IDg3LjkwMDEgNTQuMjY3OSA4Ny42NjMxIDU0LjQ2NjhDODcuNDI2MSA1NC42NjE1IDg3LjEzNjIgNTQuODIwMSA4Ni43OTM1IDU0Ljk0MjlDODYuNDU0OSA1NS4wNjU2IDg2LjA2MTQgNTUuMTI3IDg1LjYxMjggNTUuMTI3WiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cGF0aCBkPSJNMjMuMTYzNiAxMDguMDI2VjExMEgxMy4xMzA4VjEwOC4zMDRMMTguMDAzMyAxMDIuOTg5QzE4LjUzNzggMTAyLjM4NiAxOC45NTkzIDEwMS44NjUgMTkuMjY3NyAxMDEuNDI3QzE5LjU3NjEgMTAwLjk4OCAxOS43OTE5IDEwMC41OTQgMTkuOTE1MyAxMDAuMjQ1QzIwLjA0NTUgOTkuODg4MyAyMC4xMTA2IDk5LjU0MjIgMjAuMTEwNiA5OS4yMDY0QzIwLjExMDYgOTguNzMzNiAyMC4wMjE1IDk4LjMxODkgMTkuODQzMyA5Ny45NjI2QzE5LjY3MiA5Ny41OTk0IDE5LjQxODQgOTcuMzE1IDE5LjA4MjYgOTcuMTA5NEMxOC43NDY4IDk2Ljg5NjkgMTguMzM5MSA5Ni43OTA3IDE3Ljg1OTQgOTYuNzkwN0MxNy4zMDQzIDk2Ljc5MDcgMTYuODM4MyA5Ni45MTA2IDE2LjQ2MTMgOTcuMTUwNUMxNi4wODQ0IDk3LjM5MDQgMTUuOCA5Ny43MjI3IDE1LjYwODEgOTguMTQ3NkMxNS40MTYzIDk4LjU2NTcgMTUuMzIwMyA5OS4wNDU0IDE1LjMyMDMgOTkuNTg2OEgxMi44NDI5QzEyLjg0MjkgOTguNzE2NCAxMy4wNDE3IDk3LjkyMTUgMTMuNDM5MSA5Ny4yMDE5QzEzLjgzNjYgOTYuNDc1NSAxNC40MTIzIDk1Ljg5OTggMTUuMTY2MSA5NS40NzQ5QzE1LjkyIDk1LjA0MzIgMTYuODI4IDk0LjgyNzMgMTcuODkwMiA5NC44MjczQzE4Ljg5MDggOTQuODI3MyAxOS43NDA1IDk0Ljk5NTIgMjAuNDM5NiA5NS4zMzFDMjEuMTM4NiA5NS42NjY4IDIxLjY2OTcgOTYuMTQzMSAyMi4wMzI5IDk2Ljc1OTlDMjIuNDAzIDk3LjM3NjcgMjIuNTg4IDk4LjEwNjUgMjIuNTg4IDk4Ljk0OTRDMjIuNTg4IDk5LjQxNTQgMjIuNTEyNiA5OS44NzggMjIuMzYxOCAxMDAuMzM3QzIyLjIxMTEgMTAwLjc5NiAyMS45OTUyIDEwMS4yNTUgMjEuNzE0MiAxMDEuNzE1QzIxLjQ0MDEgMTAyLjE2NyAyMS4xMTQ2IDEwMi42MjMgMjAuNzM3NyAxMDMuMDgyQzIwLjM2MDcgMTAzLjUzNCAxOS45NDYxIDEwMy45OTMgMTkuNDkzOCAxMDQuNDU5TDE2LjI1NTggMTA4LjAyNkgyMy4xNjM2Wk0zNS4xMjkxIDEwOC4wMjZWMTEwSDI1LjA5NjJWMTA4LjMwNEwyOS45Njg3IDEwMi45ODlDMzAuNTAzMyAxMDIuMzg2IDMwLjkyNDcgMTAxLjg2NSAzMS4yMzMxIDEwMS40MjdDMzEuNTQxNSAxMDAuOTg4IDMxLjc1NzQgMTAwLjU5NCAzMS44ODA3IDEwMC4yNDVDMzIuMDExIDk5Ljg4ODMgMzIuMDc2MSA5OS41NDIyIDMyLjA3NjEgOTkuMjA2NEMzMi4wNzYxIDk4LjczMzYgMzEuOTg3IDk4LjMxODkgMzEuODA4OCA5Ny45NjI2QzMxLjYzNzUgOTcuNTk5NCAzMS4zODM5IDk3LjMxNSAzMS4wNDgxIDk3LjEwOTRDMzAuNzEyMyA5Ni44OTY5IDMwLjMwNDUgOTYuNzkwNyAyOS44MjQ4IDk2Ljc5MDdDMjkuMjY5NyA5Ni43OTA3IDI4LjgwMzcgOTYuOTEwNiAyOC40MjY4IDk3LjE1MDVDMjguMDQ5OSA5Ny4zOTA0IDI3Ljc2NTUgOTcuNzIyNyAyNy41NzM2IDk4LjE0NzZDMjcuMzgxNyA5OC41NjU3IDI3LjI4NTggOTkuMDQ1NCAyNy4yODU4IDk5LjU4NjhIMjQuODA4NEMyNC44MDg0IDk4LjcxNjQgMjUuMDA3MSA5Ny45MjE1IDI1LjQwNDYgOTcuMjAxOUMyNS44MDIxIDk2LjQ3NTUgMjYuMzc3NyA5NS44OTk4IDI3LjEzMTYgOTUuNDc0OUMyNy44ODU0IDk1LjA0MzIgMjguNzkzNCA5NC44MjczIDI5Ljg1NTcgOTQuODI3M0MzMC44NTYyIDk0LjgyNzMgMzEuNzA2IDk0Ljk5NTIgMzIuNDA1IDk1LjMzMUMzMy4xMDQgOTUuNjY2OCAzMy42MzUxIDk2LjE0MzEgMzMuOTk4MyA5Ni43NTk5QzM0LjM2ODQgOTcuMzc2NyAzNC41NTM0IDk4LjEwNjUgMzQuNTUzNCA5OC45NDk0QzM0LjU1MzQgOTkuNDE1NCAzNC40NzgxIDk5Ljg3OCAzNC4zMjczIDEwMC4zMzdDMzQuMTc2NSAxMDAuNzk2IDMzLjk2MDYgMTAxLjI1NSAzMy42Nzk3IDEwMS43MTVDMzMuNDA1NiAxMDIuMTY3IDMzLjA4IDEwMi42MjMgMzIuNzAzMSAxMDMuMDgyQzMyLjMyNjIgMTAzLjUzNCAzMS45MTE2IDEwMy45OTMgMzEuNDU5MyAxMDQuNDU5TDI4LjIyMTIgMTA4LjAyNkgzNS4xMjkxWiIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC44NyIvPgo8cGF0aCBkPSJNNDEuMzI2MSA5Ny41NzJDNDEuMzI2MSA5Ny4wNzE3IDQxLjQ0OTQgOTYuNjEyNSA0MS42OTYxIDk2LjE5NDVDNDEuOTQyOCA5NS43NzY1IDQyLjI3MTggOTUuNDQ0MSA0Mi42ODMgOTUuMTk3NEM0My4xMDEgOTQuOTQzOCA0My41NTMzIDk0LjgxNyA0NC4wMzk5IDk0LjgxN0M0NC41MzMzIDk0LjgxNyA0NC45ODIyIDk0Ljk0MzggNDUuMzg2NSA5NS4xOTc0QzQ1Ljc5MDggOTUuNDQ0MSA0Ni4xMTI5IDk1Ljc3NjUgNDYuMzUyOCA5Ni4xOTQ1QzQ2LjU5OTUgOTYuNjEyNSA0Ni43MjI5IDk3LjA3MTcgNDYuNzIyOSA5Ny41NzJDNDYuNzIyOSA5OC4wNzIyIDQ2LjU5OTUgOTguNTMxNCA0Ni4zNTI4IDk4Ljk0OTRDNDYuMTEyOSA5OS4zNjA2IDQ1Ljc5MDggOTkuNjg2MSA0NS4zODY1IDk5LjkyNkM0NC45ODIyIDEwMC4xNjYgNDQuNTMzMyAxMDAuMjg2IDQ0LjAzOTkgMTAwLjI4NkM0My41NTMzIDEwMC4yODYgNDMuMTAxIDEwMC4xNjYgNDIuNjgzIDk5LjkyNkM0Mi4yNzE4IDk5LjY4NjEgNDEuOTQyOCA5OS4zNjA2IDQxLjY5NjEgOTguOTQ5NEM0MS40NDk0IDk4LjUzMTQgNDEuMzI2MSA5OC4wNzIyIDQxLjMyNjEgOTcuNTcyWk00Mi43MTM4IDk3LjU3MkM0Mi43MTM4IDk3Ljk0MiA0Mi44NDQgOTguMjUzOCA0My4xMDQ0IDk4LjUwNzRDNDMuMzY0OSA5OC43NTQxIDQzLjY3NjcgOTguODc3NSA0NC4wMzk5IDk4Ljg3NzVDNDQuNDAzMSA5OC44Nzc1IDQ0LjcwODEgOTguNzU0MSA0NC45NTQ4IDk4LjUwNzRDNDUuMjAxNSA5OC4yNjA3IDQ1LjMyNDggOTcuOTQ4OSA0NS4zMjQ4IDk3LjU3MkM0NS4zMjQ4IDk3LjE4ODIgNDUuMjAxNSA5Ni44Njk1IDQ0Ljk1NDggOTYuNjE2QzQ0LjcwODEgOTYuMzYyNCA0NC40MDMxIDk2LjIzNTYgNDQuMDM5OSA5Ni4yMzU2QzQzLjY3NjcgOTYuMjM1NiA0My4zNjQ5IDk2LjM2MjQgNDMuMTA0NCA5Ni42MTZDNDIuODQ0IDk2Ljg2OTUgNDIuNzEzOCA5Ny4xODgyIDQyLjcxMzggOTcuNTcyWk01OC40MjEgMTA1LjEyN0g2MC45OTA5QzYwLjkwODcgMTA2LjEwNyA2MC42MzQ2IDEwNi45ODEgNjAuMTY4NiAxMDcuNzQ5QzU5LjcwMjYgMTA4LjUwOSA1OS4wNDgxIDEwOS4xMDkgNTguMjA1MiAxMDkuNTQ4QzU3LjM2MjIgMTA5Ljk4NiA1Ni4zMzc3IDExMC4yMDYgNTUuMTMxNiAxMTAuMjA2QzU0LjIwNjQgMTEwLjIwNiA1My4zNzM4IDExMC4wNDEgNTIuNjMzNiAxMDkuNzEyQzUxLjg5MzUgMTA5LjM3NiA1MS4yNTk2IDEwOC45MDQgNTAuNzMxOSAxMDguMjk0QzUwLjIwNDIgMTA3LjY3NyA0OS43OTk5IDEwNi45MzMgNDkuNTE4OSAxMDYuMDYzQzQ5LjI0NDggMTA1LjE5MyA0OS4xMDc3IDEwNC4yMTkgNDkuMTA3NyAxMDMuMTQ0VjEwMS45QzQ5LjEwNzcgMTAwLjgyNCA0OS4yNDgyIDk5Ljg1MDYgNDkuNTI5MiA5OC45ODAzQzQ5LjgxNyA5OC4xMDk5IDUwLjIyODIgOTcuMzY2NCA1MC43NjI3IDk2Ljc0OTZDNTEuMjk3MyA5Ni4xMjYgNTEuOTM4IDk1LjY0OTcgNTIuNjg1IDk1LjMyMDdDNTMuNDM4OSA5NC45OTE4IDU0LjI4NTIgOTQuODI3MyA1NS4yMjQxIDk0LjgyNzNDNTYuNDE2NSA5NC44MjczIDU3LjQyMzkgOTUuMDQ2NiA1OC4yNDYzIDk1LjQ4NTJDNTkuMDY4NyA5NS45MjM4IDU5LjcwNiA5Ni41MzAzIDYwLjE1ODMgOTcuMzA0N0M2MC42MTc0IDk4LjA3OTEgNjAuODk4NCA5OC45NjY2IDYxLjAwMTIgOTkuOTY3MUg1OC40MzEzQzU4LjM2MjggOTkuMzIyOSA1OC4yMTIgOTguNzcxMyA1Ny45NzkgOTguMzEyMUM1Ny43NTI5IDk3Ljg1MjkgNTcuNDE3MSA5Ny41MDM0IDU2Ljk3MTYgOTcuMjYzNkM1Ni41MjYyIDk3LjAxNjkgNTUuOTQzNyA5Ni44OTM1IDU1LjIyNDEgOTYuODkzNUM1NC42MzQ3IDk2Ljg5MzUgNTQuMTIwNyA5Ny4wMDMyIDUzLjY4MjEgOTcuMjIyNUM1My4yNDM1IDk3LjQ0MTggNTIuODc2OSA5Ny43NjM5IDUyLjU4MjIgOTguMTg4N0M1Mi4yODc1IDk4LjYxMzYgNTIuMDY0OCA5OS4xMzc5IDUxLjkxNDEgOTkuNzYxNUM1MS43NzAxIDEwMC4zNzggNTEuNjk4MiAxMDEuMDg0IDUxLjY5ODIgMTAxLjg3OVYxMDMuMTQ0QzUxLjY5ODIgMTAzLjg5NyA1MS43NjMzIDEwNC41ODMgNTEuODkzNSAxMDUuMTk5QzUyLjAzMDYgMTA1LjgwOSA1Mi4yMzYxIDEwNi4zMzQgNTIuNTEwMyAxMDYuNzcyQzUyLjc5MTIgMTA3LjIxMSA1My4xNDc2IDEwNy41NSA1My41NzkzIDEwNy43OUM1NC4wMTExIDEwOC4wMyA1NC41Mjg1IDEwOC4xNSA1NS4xMzE2IDEwOC4xNUM1NS44NjQ4IDEwOC4xNSA1Ni40NTc2IDEwOC4wMzMgNTYuOTA5OSAxMDcuOEM1Ny4zNjkxIDEwNy41NjcgNTcuNzE1MiAxMDcuMjI4IDU3Ljk0ODIgMTA2Ljc4MkM1OC4xODggMTA2LjMzIDU4LjM0NTcgMTA1Ljc3OSA1OC40MjEgMTA1LjEyN1oiIGZpbGw9ImJsYWNrIiBmaWxsLW9wYWNpdHk9IjAuODciLz4KPHBhdGggZD0iTTY4Ljc4MiAxMTEuMTQ4TDc5LjA3MzIgMTA0Ljg3NkM4MC43NjQ3IDEwMy44NDUgODIuMDcgMTAyLjI4NiA4Mi43ODc1IDEwMC40NEw4NS44OTA4IDkyLjQ1NDVDODguMjQ5NSA4Ni4zODQ5IDk2LjgzNzkgODYuMzg0OSA5OS4xOTY3IDkyLjQ1NDVWOTIuNDU0NUMxMDEuMjczIDk3Ljc5NzkgMTA4LjQ4MyA5OC42MzY4IDExMS43MzEgOTMuOTEzTDExNS40MDMgODguNTcyNUMxMTcuNjY3IDg1LjI3OTIgMTIyLjMyOCA4NC43NjUgMTI1LjI1NiA4Ny40ODU2Vjg3LjQ4NTZDMTI4LjUzMyA5MC41MzA0IDEzMy44MyA4OS40NjY1IDEzNS42NzcgODUuMzkyN0wxMzguNTAxIDc5LjE2MzFDMTQxLjYxMiA3Mi4zMDIxIDE1MS4yNjQgNzIuMDQ5MSAxNTQuNzMgNzguNzM3OUwxNjAuMTcgODkuMjM3QzE2Mi4yNjEgOTMuMjcyOCAxNjcuMDI4IDk1LjEyMjkgMTcxLjI5NCA5My41NTQ2TDE3NC42MTIgOTIuMzM0OEMxNzYuODM4IDkxLjUxNjYgMTc5LjI5NyA5MS42MDc1IDE4MS40NTYgOTIuNTg4MUwxODkuNjI3IDk2LjI5ODgiIHN0cm9rZT0iIzNGNTJERCIgc3Ryb2tlLXdpZHRoPSIxLjUwMzc2IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPC9zdmc+Cg==", "description": "Displays a single entity historical telemetry values as a simplified chart. Optionally may display the corresponding latest telemetry value.", "descriptor": { "type": "timeseries", @@ -22,6 +22,5 @@ "basicModeDirective": "tb-value-chart-card-basic-config", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"latestDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Latest\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0\",\"settings\":{\"layout\":\"left\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":28,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Simple Value and chart card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"iconSize\":\"18px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"useDashboardTimewindow\":true,\"decimals\":0,\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"borderRadius\":null,\"units\":\"°C\",\"displayTimewindow\":true,\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":43200000,\"fixedTimewindow\":{\"startTimeMs\":1697382151041,\"endTimeMs\":1697468551041},\"quickInterval\":\"CURRENT_MONTH_SO_FAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000}},\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}" }, - "externalId": null, "tags": null } \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html index 249acae52a..9f8d580bbc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/progress-bar-basic-config.component.html @@ -86,7 +86,7 @@
    -
    +
    {{ 'widgets.progress-bar.value' | translate }} @@ -104,7 +104,7 @@
    -
    +
    {{ 'widgets.progress-bar.range' | translate }}
    widgets.progress-bar.min
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html index de641b8808..31c8276666 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/progress-bar-widget-settings.component.html @@ -35,7 +35,7 @@ {{ 'widgets.progress-bar.auto-scale' | translate }}
    -
    +
    {{ 'widgets.progress-bar.value' | translate }} @@ -48,7 +48,7 @@
    -
    +
    {{ 'widgets.progress-bar.range' | translate }}
    widgets.progress-bar.min
    From 4dc3d0ee78fdf073c460553d2ed25af6352d27fd Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Feb 2024 17:03:15 +0200 Subject: [PATCH 126/137] UI: Minor improvement --- .../home/components/widget/lib/rpc/slider-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts index a60cfd84a0..74b3dab326 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts @@ -298,7 +298,7 @@ export class SliderWidgetComponent extends } let minAspect = 0.2; - let avgContentHeight = 45; + let avgContentHeight = 35; if (this.showTicks) { minAspect += 0.1; avgContentHeight += 20; From a755d4d8518c64d47123026520a7c5a1000754ac Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Feb 2024 18:14:27 +0200 Subject: [PATCH 127/137] UI: Improve slider widget step calculation. --- .../home/components/widget/lib/rpc/slider-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts index 74b3dab326..37b5319d44 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.component.ts @@ -176,7 +176,7 @@ export class SliderWidgetComponent extends if (this.settings.showTickMarks) { const range = this.settings.tickMax - this.settings.tickMin; - this.sliderStep = Math.floor(range / (this.settings.tickMarksCount - 1)); + this.sliderStep = range / (this.settings.tickMarksCount - 1); } const mainColorInstance = tinycolor(this.settings.mainColor); From 5f70100118197d4ff1c70ce71b858da78d8a545e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Feb 2024 18:42:29 +0200 Subject: [PATCH 128/137] UI: Add value converter type to set value settings. --- .../config/basic/rpc/slider-basic-config.component.html | 3 ++- .../components/widget/lib/action/action-widget.models.ts | 4 ++++ .../components/widget/lib/rpc/slider-widget.models.ts | 2 +- .../action/set-value-action-settings-panel.component.html | 3 ++- .../action/set-value-action-settings-panel.component.ts | 6 ++++++ .../common/action/set-value-action-settings.component.ts | 8 ++++++++ .../control/slider-widget-settings.component.html | 3 ++- .../app/shared/models/action-widget-settings.models.ts | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 9 files changed, 27 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html index 76c4ff9ea3..a8d84d4dc4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html @@ -23,7 +23,7 @@
    widgets.slider.initial-value
    widgets.slider.on-value-change
    { constructor(protected settings: ValueToDataSettings) { switch (settings.type) { + case ValueToDataType.VALUE: + break; case ValueToDataType.CONSTANT: this.constantValue = this.settings.constantValue; break; @@ -309,6 +311,8 @@ export class ValueToDataConverter { valueToData(value: V): any { switch (this.settings.type) { + case ValueToDataType.VALUE: + return value; case ValueToDataType.CONSTANT: return this.constantValue; case ValueToDataType.FUNCTION: diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts index 78603bc058..c6fe1e290a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/slider-widget.models.ts @@ -138,7 +138,7 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = { key: 'state' }, valueToData: { - type: ValueToDataType.FUNCTION, + type: ValueToDataType.VALUE, constantValue: 0, valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html index b1fccafac0..4cf5857612 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html @@ -97,7 +97,8 @@
    {{ (setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC ? 'widgets.value-action.parameters' : 'widgets.value-action.value') | translate }}
    - {{ 'widgets.value-action.converter-constant' | translate }} + {{ 'widgets.value-action.converter-value' | translate }} + {{ 'widgets.value-action.converter-constant' | translate }} {{ 'widgets.value-action.converter-function' | translate }} {{ 'widgets.value-action.converter-none' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts index d718168b11..819ed2a3fc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.ts @@ -32,6 +32,7 @@ import { TargetDevice, widgetType } from '@shared/models/widget.models'; import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; import { IAliasController } from '@core/api/widget-api.models'; import { WidgetService } from '@core/http/widget.service'; +import { ValueType } from '@shared/models/constants'; @Component({ selector: 'tb-set-value-action-settings-panel', @@ -45,6 +46,9 @@ export class SetValueActionSettingsPanelComponent extends PageComponent implemen @Input() panelTitle: string; + @Input() + valueType = ValueType.BOOLEAN; + @Input() setValueSettings: SetValueSettings; @@ -79,6 +83,8 @@ export class SetValueActionSettingsPanelComponent extends PageComponent implemen functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + ValueType = ValueType; + setValueSettingsFormGroup: UntypedFormGroup; constructor(private fb: UntypedFormBuilder, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts index fce1e23673..7b4c8e0428 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts @@ -36,6 +36,7 @@ import { isDefinedAndNotNull } from '@core/utils'; import { SetValueActionSettingsPanelComponent } from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component'; +import { ValueType } from '@shared/models/constants'; @Component({ selector: 'tb-set-value-action-settings', @@ -58,6 +59,9 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce @Input() panelTitle: string; + @Input() + valueType = ValueType.BOOLEAN; + @Input() aliasController: IAliasController; @@ -114,6 +118,7 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce const ctx: any = { setValueSettings: this.modelValue, panelTitle: this.panelTitle, + valueType: this.valueType, aliasController: this.aliasController, targetDevice: this.targetDevice, widgetType: this.widgetType @@ -137,6 +142,9 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce private updateDisplayValue() { let value: any; switch (this.modelValue.valueToData.type) { + case ValueToDataType.VALUE: + value = 'value'; + break; case ValueToDataType.CONSTANT: value = this.modelValue.valueToData.constantValue; break; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html index 9ef4a603ec..0f3c3f11a3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slider-widget-settings.component.html @@ -22,7 +22,7 @@
    widgets.slider.initial-value
    widgets.slider.on-value-change
    ( ); export enum ValueToDataType { + VALUE = 'VALUE', CONSTANT = 'CONSTANT', FUNCTION = 'FUNCTION', NONE = 'NONE' diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 296c56c020..c71f33fca7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6638,6 +6638,7 @@ "converter-none": "None", "converter-function": "Function", "converter-constant": "Constant", + "converter-value": "Value", "parse-value-function": "Parse value function", "state-when-result-is": "'{{state}}' when result is", "parameters": "Parameters", From c5c18202c21dfb7714b67efb8ce501e50773bdaf Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 14:13:40 +0200 Subject: [PATCH 129/137] Improve connectivity events routing logic --- ...ClusteredRuleEngineDeviceStateManager.java | 110 -------- .../DefaultRuleEngineDeviceStateManager.java | 194 +++++++++++++ .../LocalRuleEngineDeviceStateManager.java | 85 ------ ...teredRuleEngineDeviceStateManagerTest.java | 150 ---------- ...faultRuleEngineDeviceStateManagerTest.java | 266 ++++++++++++++++++ ...LocalRuleEngineDeviceStateManagerTest.java | 131 --------- 6 files changed, 460 insertions(+), 476 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java create mode 100644 application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java deleted file mode 100644 index 2a73d3f544..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManager.java +++ /dev/null @@ -1,110 +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. - */ -package org.thingsboard.server.service.state; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.SimpleTbQueueCallback; -import org.thingsboard.server.queue.util.TbRuleEngineComponent; - -@Slf4j -@Service -@TbRuleEngineComponent -@RequiredArgsConstructor -public class ClusteredRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { - - private final TbClusterService clusterService; - - @Override - public void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback) { - var tenantUuid = tenantId.getId(); - var deviceUuid = deviceId.getId(); - var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastConnectTime(connectTime) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceConnectMsg(deviceConnectMsg) - .build(); - log.trace("[{}][{}] Sending device connect message to core. Connect time: [{}].", tenantUuid, deviceUuid, connectTime); - clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); - } - - @Override - public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback) { - var tenantUuid = tenantId.getId(); - var deviceUuid = deviceId.getId(); - var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastActivityTime(activityTime) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceActivityMsg(deviceActivityMsg) - .build(); - log.trace("[{}][{}] Sending device activity message to core. Activity time: [{}].", tenantUuid, deviceUuid, activityTime); - clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); - } - - @Override - public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback) { - var tenantUuid = tenantId.getId(); - var deviceUuid = deviceId.getId(); - var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastDisconnectTime(disconnectTime) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceDisconnectMsg(deviceDisconnectMsg) - .build(); - log.trace("[{}][{}] Sending device disconnect message to core. Disconnect time: [{}].", tenantUuid, deviceUuid, disconnectTime); - clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); - } - - @Override - public void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback) { - var tenantUuid = tenantId.getId(); - var deviceUuid = deviceId.getId(); - var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() - .setTenantIdMSB(tenantUuid.getMostSignificantBits()) - .setTenantIdLSB(tenantUuid.getLeastSignificantBits()) - .setDeviceIdMSB(deviceUuid.getMostSignificantBits()) - .setDeviceIdLSB(deviceUuid.getLeastSignificantBits()) - .setLastInactivityTime(inactivityTime) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceInactivityMsg(deviceInactivityMsg) - .build(); - log.trace("[{}][{}] Sending device inactivity message to core. Inactivity time: [{}].", tenantUuid, deviceUuid, inactivityTime); - clusterService.pushMsgToCore(tenantId, deviceId, toCoreMsg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java new file mode 100644 index 0000000000..087f470fed --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java @@ -0,0 +1,194 @@ +/** + * 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.state; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.SimpleTbQueueCallback; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; + +import java.util.Optional; +import java.util.UUID; + +@Slf4j +@Service +public class DefaultRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { + + private final TbServiceInfoProvider serviceInfoProvider; + private final PartitionService partitionService; + + private final Optional deviceStateService; + private final TbClusterService clusterService; + + public DefaultRuleEngineDeviceStateManager( + TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService, + Optional deviceStateServiceOptional, TbClusterService clusterService + ) { + this.serviceInfoProvider = serviceInfoProvider; + this.partitionService = partitionService; + this.deviceStateService = deviceStateServiceOptional; + this.clusterService = clusterService; + } + + @Getter + private abstract static class ConnectivityEventInfo { + + private final TenantId tenantId; + private final DeviceId deviceId; + private final long eventTime; + + private ConnectivityEventInfo(TenantId tenantId, DeviceId deviceId, long eventTime) { + this.tenantId = tenantId; + this.deviceId = deviceId; + this.eventTime = eventTime; + } + + abstract void forwardToLocalService(); + + abstract TransportProtos.ToCoreMsg toQueueMsg(); + + } + + @Override + public void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback) { + routeEvent(new ConnectivityEventInfo(tenantId, deviceId, connectTime) { + @Override + void forwardToLocalService() { + deviceStateService.ifPresent(service -> service.onDeviceConnect(tenantId, deviceId, connectTime)); + } + + @Override + TransportProtos.ToCoreMsg toQueueMsg() { + var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastConnectTime(connectTime) + .build(); + return TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(deviceConnectMsg) + .build(); + } + }, callback); + } + + @Override + public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback) { + routeEvent(new ConnectivityEventInfo(tenantId, deviceId, activityTime) { + @Override + void forwardToLocalService() { + deviceStateService.ifPresent(service -> service.onDeviceActivity(tenantId, deviceId, activityTime)); + } + + @Override + TransportProtos.ToCoreMsg toQueueMsg() { + var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastActivityTime(activityTime) + .build(); + return TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(deviceActivityMsg) + .build(); + } + }, callback); + } + + @Override + public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback) { + routeEvent(new ConnectivityEventInfo(tenantId, deviceId, disconnectTime) { + @Override + void forwardToLocalService() { + deviceStateService.ifPresent(service -> service.onDeviceDisconnect(tenantId, deviceId, disconnectTime)); + } + + @Override + TransportProtos.ToCoreMsg toQueueMsg() { + var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastDisconnectTime(disconnectTime) + .build(); + return TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(deviceDisconnectMsg) + .build(); + } + }, callback); + } + + @Override + public void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback) { + routeEvent(new ConnectivityEventInfo(tenantId, deviceId, inactivityTime) { + @Override + void forwardToLocalService() { + deviceStateService.ifPresent(service -> service.onDeviceInactivity(tenantId, deviceId, inactivityTime)); + } + + @Override + TransportProtos.ToCoreMsg toQueueMsg() { + var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastInactivityTime(inactivityTime) + .build(); + return TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(deviceInactivityMsg) + .build(); + } + }, callback); + } + + private void routeEvent(ConnectivityEventInfo eventInfo, TbCallback callback) { + var tenantId = eventInfo.getTenantId(); + var deviceId = eventInfo.getDeviceId(); + long eventTime = eventInfo.getEventTime(); + + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); + if (serviceInfoProvider.isService(ServiceType.TB_CORE) && tpi.isMyPartition() && deviceStateService.isPresent()) { + log.info("[{}][{}] Forwarding device connectivity event to local service. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); + try { + eventInfo.forwardToLocalService(); + } catch (Exception e) { + log.error("[{}][{}] Failed to process device connectivity event. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime, e); + callback.onFailure(e); + return; + } + callback.onSuccess(); + } else { + TransportProtos.ToCoreMsg msg = eventInfo.toQueueMsg(); + log.info("[{}][{}] Sending device connectivity message to core. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); + clusterService.pushMsgToCore(tpi, UUID.randomUUID(), msg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java deleted file mode 100644 index 180aea9ecb..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManager.java +++ /dev/null @@ -1,85 +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. - */ -package org.thingsboard.server.service.state; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.queue.util.TbCoreComponent; - -@Slf4j -@Service -@Primary -@TbCoreComponent -@RequiredArgsConstructor -public class LocalRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { - - private final DeviceStateService deviceStateService; - - @Override - public void onDeviceConnect(TenantId tenantId, DeviceId deviceId, long connectTime, TbCallback callback) { - try { - deviceStateService.onDeviceConnect(tenantId, deviceId, connectTime); - } catch (Exception e) { - log.error("[{}][{}] Failed to process device connect event. Connect time: [{}].", tenantId.getId(), deviceId.getId(), connectTime, e); - callback.onFailure(e); - return; - } - callback.onSuccess(); - } - - @Override - public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long activityTime, TbCallback callback) { - try { - deviceStateService.onDeviceActivity(tenantId, deviceId, activityTime); - } catch (Exception e) { - log.error("[{}][{}] Failed to process device activity event. Activity time: [{}].", tenantId.getId(), deviceId.getId(), activityTime, e); - callback.onFailure(e); - return; - } - callback.onSuccess(); - } - - @Override - public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId, long disconnectTime, TbCallback callback) { - try { - deviceStateService.onDeviceDisconnect(tenantId, deviceId, disconnectTime); - } catch (Exception e) { - log.error("[{}][{}] Failed to process device disconnect event. Disconnect time: [{}].", tenantId.getId(), deviceId.getId(), disconnectTime, e); - callback.onFailure(e); - return; - } - callback.onSuccess(); - } - - @Override - public void onDeviceInactivity(TenantId tenantId, DeviceId deviceId, long inactivityTime, TbCallback callback) { - try { - deviceStateService.onDeviceInactivity(tenantId, deviceId, inactivityTime); - } catch (Exception e) { - log.error("[{}][{}] Failed to process device inactivity event. Inactivity time: [{}].", tenantId.getId(), deviceId.getId(), inactivityTime, e); - callback.onFailure(e); - return; - } - callback.onSuccess(); - } - -} diff --git a/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java b/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java deleted file mode 100644 index e972b49347..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/state/ClusteredRuleEngineDeviceStateManagerTest.java +++ /dev/null @@ -1,150 +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. - */ -package org.thingsboard.server.service.state; - -import org.junit.jupiter.api.BeforeEach; -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.MethodSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.queue.TbQueueMsgMetadata; - -import java.util.UUID; -import java.util.stream.Stream; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.then; - -@ExtendWith(MockitoExtension.class) -public class ClusteredRuleEngineDeviceStateManagerTest { - - @Mock - private static TbClusterService tbClusterServiceMock; - @Mock - private static TbCallback tbCallbackMock; - @Mock - private static TbQueueMsgMetadata metadataMock; - @Captor - private static ArgumentCaptor queueCallbackCaptor; - private static ClusteredRuleEngineDeviceStateManager deviceStateManager; - - private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002")); - private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002"); - private static final long EVENT_TS = System.currentTimeMillis(); - - @BeforeEach - public void setup() { - deviceStateManager = new ClusteredRuleEngineDeviceStateManager(tbClusterServiceMock); - } - - @ParameterizedTest - @MethodSource - public void givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) { - // WHEN - onDeviceAction.run(); - - // THEN - actionVerification.run(); - - TbQueueCallback callback = queueCallbackCaptor.getValue(); - callback.onSuccess(metadataMock); - then(tbCallbackMock).should().onSuccess(); - - var runtimeException = new RuntimeException("Something bad happened!"); - callback.onFailure(runtimeException); - then(tbCallbackMock).should().onFailure(runtimeException); - } - - private static Stream givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback() { - return Stream.of( - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> { - var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastConnectTime(EVENT_TS) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceConnectMsg(deviceConnectMsg) - .build(); - then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); - } - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> { - var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastActivityTime(EVENT_TS) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceActivityMsg(deviceActivityMsg) - .build(); - then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); - } - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> { - var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastDisconnectTime(EVENT_TS) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceDisconnectMsg(deviceDisconnectMsg) - .build(); - then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); - } - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> { - var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastInactivityTime(EVENT_TS) - .build(); - var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceInactivityMsg(deviceInactivityMsg) - .build(); - then(tbClusterServiceMock).should().pushMsgToCore(eq(TENANT_ID), eq(DEVICE_ID), eq(toCoreMsg), queueCallbackCaptor.capture()); - } - ) - ); - } - -} diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java new file mode 100644 index 0000000000..c97305c170 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManagerTest.java @@ -0,0 +1,266 @@ +/** + * 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.state; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +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.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +public class DefaultRuleEngineDeviceStateManagerTest { + + @Mock + private static DeviceStateService deviceStateServiceMock; + @Mock + private static TbCallback tbCallbackMock; + @Mock + private static TbClusterService clusterServiceMock; + @Mock + private static TbQueueMsgMetadata metadataMock; + + @Mock + private TbServiceInfoProvider serviceInfoProviderMock; + @Mock + private PartitionService partitionServiceMock; + + @Captor + private static ArgumentCaptor queueCallbackCaptor; + + private static DefaultRuleEngineDeviceStateManager deviceStateManager; + + private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002")); + private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002"); + private static final long EVENT_TS = System.currentTimeMillis(); + private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException("Something bad happened!"); + private static final TopicPartitionInfo MY_TPI = TopicPartitionInfo.builder().myPartition(true).build(); + private static final TopicPartitionInfo EXTERNAL_TPI = TopicPartitionInfo.builder().myPartition(false).build(); + + @BeforeEach + public void setup() { + deviceStateManager = new DefaultRuleEngineDeviceStateManager(serviceInfoProviderMock, partitionServiceMock, Optional.of(deviceStateServiceMock), clusterServiceMock); + } + + @ParameterizedTest + @DisplayName("Given event should be routed to local service and event processed has succeeded, " + + "when onDeviceX() is called, then should route event to local service and call onSuccess() callback.") + @MethodSource + public void givenRoutedToLocalAndProcessingSuccess_whenOnDeviceAction_thenShouldCallLocalServiceAndSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) { + // GIVEN + given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(true); + given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(MY_TPI); + + onDeviceAction.run(); + + // THEN + actionVerification.run(); + + then(clusterServiceMock).shouldHaveNoInteractions(); + then(tbCallbackMock).should().onSuccess(); + then(tbCallbackMock).should(never()).onFailure(any()); + } + + private static Stream givenRoutedToLocalAndProcessingSuccess_whenOnDeviceAction_thenShouldCallLocalServiceAndSuccessCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ) + ); + } + + @ParameterizedTest + @DisplayName("Given event should be routed to local service and event processed has failed, " + + "when onDeviceX() is called, then should route event to local service and call onFailure() callback.") + @MethodSource + public void givenRoutedToLocalAndProcessingFailure_whenOnDeviceAction_thenShouldCallLocalServiceAndFailureCallback( + Runnable exceptionThrowSetup, Runnable onDeviceAction, Runnable actionVerification + ) { + // GIVEN + given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(true); + given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(MY_TPI); + + exceptionThrowSetup.run(); + + // WHEN + onDeviceAction.run(); + + // THEN + actionVerification.run(); + + then(clusterServiceMock).shouldHaveNoInteractions(); + then(tbCallbackMock).should(never()).onSuccess(); + then(tbCallbackMock).should().onFailure(RUNTIME_EXCEPTION); + } + + private static Stream givenRoutedToLocalAndProcessingFailure_whenOnDeviceAction_thenShouldCallLocalServiceAndFailureCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) + ), + Arguments.of( + (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS), + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) + ) + ); + } + + @ParameterizedTest + @DisplayName("Given event should be routed to external service, " + + "when onDeviceX() is called, then should send correct queue message to external service with correct callback object.") + @MethodSource + public void givenRoutedToExternal_whenOnDeviceAction_thenShouldSendQueueMsgToExternalServiceWithCorrectCallback(Runnable onDeviceAction, Runnable actionVerification) { + // WHEN + ReflectionTestUtils.setField(deviceStateManager, "deviceStateService", Optional.empty()); + given(serviceInfoProviderMock.isService(ServiceType.TB_CORE)).willReturn(false); + given(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).willReturn(EXTERNAL_TPI); + + onDeviceAction.run(); + + // THEN + actionVerification.run(); + + TbQueueCallback callback = queueCallbackCaptor.getValue(); + callback.onSuccess(metadataMock); + then(tbCallbackMock).should().onSuccess(); + callback.onFailure(RUNTIME_EXCEPTION); + then(tbCallbackMock).should().onFailure(RUNTIME_EXCEPTION); + } + + private static Stream givenRoutedToExternal_whenOnDeviceAction_thenShouldSendQueueMsgToExternalServiceWithCorrectCallback() { + return Stream.of( + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceConnectMsg = TransportProtos.DeviceConnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastConnectTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceConnectMsg(deviceConnectMsg) + .build(); + then(clusterServiceMock).should().pushMsgToCore(eq(EXTERNAL_TPI), any(UUID.class), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastActivityTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(deviceActivityMsg) + .build(); + then(clusterServiceMock).should().pushMsgToCore(eq(EXTERNAL_TPI), any(UUID.class), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceDisconnectMsg = TransportProtos.DeviceDisconnectProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastDisconnectTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceDisconnectMsg(deviceDisconnectMsg) + .build(); + then(clusterServiceMock).should().pushMsgToCore(eq(EXTERNAL_TPI), any(UUID.class), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ), + Arguments.of( + (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), + (Runnable) () -> { + var deviceInactivityMsg = TransportProtos.DeviceInactivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastInactivityTime(EVENT_TS) + .build(); + var toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceInactivityMsg(deviceInactivityMsg) + .build(); + then(clusterServiceMock).should().pushMsgToCore(eq(EXTERNAL_TPI), any(UUID.class), eq(toCoreMsg), queueCallbackCaptor.capture()); + } + ) + ); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java b/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java deleted file mode 100644 index db4d71f995..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/state/LocalRuleEngineDeviceStateManagerTest.java +++ /dev/null @@ -1,131 +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. - */ -package org.thingsboard.server.service.state; - -import org.junit.jupiter.api.BeforeEach; -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.MethodSource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.TbCallback; - -import java.util.UUID; -import java.util.stream.Stream; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; - -@ExtendWith(MockitoExtension.class) -public class LocalRuleEngineDeviceStateManagerTest { - - @Mock - private static DeviceStateService deviceStateServiceMock; - @Mock - private static TbCallback tbCallbackMock; - private static LocalRuleEngineDeviceStateManager deviceStateManager; - - private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("57ab2e6c-bc4c-11ee-a506-0242ac120002")); - private static final DeviceId DEVICE_ID = DeviceId.fromString("74a9053e-bc4c-11ee-a506-0242ac120002"); - private static final long EVENT_TS = System.currentTimeMillis(); - private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException("Something bad happened!"); - - @BeforeEach - public void setup() { - deviceStateManager = new LocalRuleEngineDeviceStateManager(deviceStateServiceMock); - } - - @ParameterizedTest - @MethodSource - public void givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback(Runnable onDeviceAction, Runnable actionVerification) { - // WHEN - onDeviceAction.run(); - - // THEN - actionVerification.run(); - then(tbCallbackMock).should().onSuccess(); - then(tbCallbackMock).should(never()).onFailure(any()); - } - - private static Stream givenProcessingSuccess_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnSuccessCallback() { - return Stream.of( - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) - ) - ); - } - - @ParameterizedTest - @MethodSource - public void givenProcessingFailure_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnFailureCallback( - Runnable exceptionThrowSetup, Runnable onDeviceAction, Runnable actionVerification - ) { - // GIVEN - exceptionThrowSetup.run(); - - // WHEN - onDeviceAction.run(); - - // THEN - actionVerification.run(); - then(tbCallbackMock).should(never()).onSuccess(); - then(tbCallbackMock).should().onFailure(RUNTIME_EXCEPTION); - } - - private static Stream givenProcessingFailure_whenOnDeviceAction_thenCallsDeviceStateServiceAndOnFailureCallback() { - return Stream.of( - Arguments.of( - (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS), - (Runnable) () -> deviceStateManager.onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceConnect(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS), - (Runnable) () -> deviceStateManager.onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceActivity(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS), - (Runnable) () -> deviceStateManager.onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceDisconnect(TENANT_ID, DEVICE_ID, EVENT_TS) - ), - Arguments.of( - (Runnable) () -> doThrow(RUNTIME_EXCEPTION).when(deviceStateServiceMock).onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS), - (Runnable) () -> deviceStateManager.onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS, tbCallbackMock), - (Runnable) () -> then(deviceStateServiceMock).should().onDeviceInactivity(TENANT_ID, DEVICE_ID, EVENT_TS) - ) - ); - } - -} From c5b74ff9325cf102e107447c6fc898bdadea385e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 15 Feb 2024 14:45:38 +0200 Subject: [PATCH 130/137] Mapping of cmdId to unique sequence per subscription to fix race condition with entity data query commands --- .../service/subscription/TbSubscription.java | 2 - .../service/ws/DefaultWebSocketService.java | 28 +++++++------- .../server/service/ws/WebSocketService.java | 2 +- .../DefaultNotificationCommandsHandler.java | 8 ++-- .../sub/AbstractNotificationSubscription.java | 38 +++++++++++++++++++ .../sub/NotificationsCountSubscription.java | 6 +-- .../sub/NotificationsSubscription.java | 3 +- 7 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/notification/sub/AbstractNotificationSubscription.java diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 256a8ac89c..d1822a6ffe 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -36,8 +36,6 @@ public abstract class TbSubscription { private final TbSubscriptionType type; private final BiConsumer, T> updateProcessor; - protected final AtomicInteger sequence = new AtomicInteger(); - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 74f40e0c46..edb2d7b7fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -259,13 +259,13 @@ public class DefaultWebSocketService implements WebSocketService { } @Override - public void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update) { - sendUpdate(sessionId, update.getSubscriptionId(), update); + public void sendUpdate(String sessionId, int cmdId, TelemetrySubscriptionUpdate update) { + doSendUpdate(sessionId, cmdId, update); } @Override public void sendUpdate(String sessionId, CmdUpdate update) { - sendUpdate(sessionId, update.getCmdId(), update); + doSendUpdate(sessionId, update.getCmdId(), update); } @Override @@ -274,7 +274,7 @@ public class DefaultWebSocketService implements WebSocketService { sendUpdate(sessionRef, update); } - private void sendUpdate(String sessionId, int cmdId, T update) { + private void doSendUpdate(String sessionId, int cmdId, T update) { WsSessionMetaData md = wsSessionsMap.get(sessionId); if (md != null) { sendUpdate(md.getSessionRef(), cmdId, update); @@ -288,7 +288,7 @@ public class DefaultWebSocketService implements WebSocketService { try { msgEndpoint.close(md.getSessionRef(), status); } catch (IOException e) { - log.warn("[{}] Failed to send session close: {}", sessionId, e); + log.warn("[{}] Failed to send session close", sessionId, e); } } } @@ -439,7 +439,7 @@ public class DefaultWebSocketService implements WebSocketService { TbAttributeSubscription sub = TbAttributeSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) - .subscriptionId(cmd.getCmdId()) + .subscriptionId(sessionRef.getSessionSubIdSeq().incrementAndGet()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) .queryTs(queryTs) @@ -449,7 +449,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendUpdate(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update); } finally { subLock.unlock(); } @@ -545,7 +545,7 @@ public class DefaultWebSocketService implements WebSocketService { TbAttributeSubscription sub = TbAttributeSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) - .subscriptionId(cmd.getCmdId()) + .subscriptionId(sessionRef.getSessionSubIdSeq().incrementAndGet()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) .queryTs(queryTs) @@ -554,7 +554,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendUpdate(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update); } finally { subLock.unlock(); } @@ -643,13 +643,13 @@ public class DefaultWebSocketService implements WebSocketService { TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) - .subscriptionId(cmd.getCmdId()) + .subscriptionId(sessionRef.getSessionSubIdSeq().incrementAndGet()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendUpdate(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update); } finally { subLock.unlock(); } @@ -698,13 +698,13 @@ public class DefaultWebSocketService implements WebSocketService { TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder() .serviceId(serviceId) .sessionId(sessionId) - .subscriptionId(cmd.getCmdId()) + .subscriptionId(sessionRef.getSessionSubIdSeq().incrementAndGet()) .tenantId(sessionRef.getSecurityCtx().getTenantId()) .entityId(entityId) .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendUpdate(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update); } finally { subLock.unlock(); } @@ -836,7 +836,7 @@ public class DefaultWebSocketService implements WebSocketService { try { msgEndpoint.sendPing(md.getSessionRef(), currentTime); } catch (IOException e) { - log.warn("[{}] Failed to send ping: {}", md.getSessionRef().getSessionId(), e); + log.warn("[{}] Failed to send ping:", md.getSessionRef().getSessionId(), e); } })); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java index dea7c9813b..8f586f264e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java @@ -29,7 +29,7 @@ public interface WebSocketService { void handleCommands(WebSocketSessionRef sessionRef, WsCommandsWrapper commandsWrapper); - void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update); + void sendUpdate(String sessionId, int cmdId, TelemetrySubscriptionUpdate update); void sendUpdate(String sessionId, CmdUpdate update); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index a5368e0f5e..f868a9d4f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -115,7 +115,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId()); int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId()); - subscription.getUnreadCounter().set(unreadCount); + subscription.getTotalUnreadCounter().set(unreadCount); } @@ -196,20 +196,20 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) { log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); if (update.isCreated()) { - subscription.getUnreadCounter().incrementAndGet(); + subscription.getTotalUnreadCounter().incrementAndGet(); sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } else if (update.isUpdated()) { if (update.getNewStatus() == NotificationStatus.READ) { if (update.isAllNotifications()) { fetchUnreadNotificationsCount(subscription); } else { - subscription.getUnreadCounter().decrementAndGet(); + subscription.getTotalUnreadCounter().decrementAndGet(); } sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } } else if (update.isDeleted()) { if (update.getNotification().getStatus() != NotificationStatus.READ) { - subscription.getUnreadCounter().decrementAndGet(); + subscription.getTotalUnreadCounter().decrementAndGet(); sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/AbstractNotificationSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/AbstractNotificationSubscription.java new file mode 100644 index 0000000000..37ba69589b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/AbstractNotificationSubscription.java @@ -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. + */ +package org.thingsboard.server.service.ws.notification.sub; + + +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.subscription.TbSubscription; +import org.thingsboard.server.service.subscription.TbSubscriptionType; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +@Getter +public abstract class AbstractNotificationSubscription extends TbSubscription { + + protected final AtomicInteger sequence = new AtomicInteger(); + protected final AtomicInteger totalUnreadCounter = new AtomicInteger(); + + public AbstractNotificationSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, TbSubscriptionType type, BiConsumer, T> updateProcessor) { + super(serviceId, sessionId, subscriptionId, tenantId, entityId, type, updateProcessor); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java index 145f69d6b6..1e812c3b31 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java @@ -27,9 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @Getter -public class NotificationsCountSubscription extends TbSubscription { - - private final AtomicInteger unreadCounter = new AtomicInteger(); +public class NotificationsCountSubscription extends AbstractNotificationSubscription { @Builder public NotificationsCountSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, @@ -40,7 +38,7 @@ public class NotificationsCountSubscription extends TbSubscription { +public class NotificationsSubscription extends AbstractNotificationSubscription { private final Map latestUnreadNotifications = new HashMap<>(); private final int limit; - private final AtomicInteger totalUnreadCounter = new AtomicInteger(); @Builder public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, From 6e8e26c777157493c0db3b098a0740e0238c5af7 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 15 Feb 2024 16:12:49 +0200 Subject: [PATCH 131/137] UI: Update rule core config --- .../static/rulenode/rulenode-core-config.js | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 5bfa26ada4..d0a1b0f235 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,25 +1,25 @@ -System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/common","@angular/material/checkbox","@angular/material/input","@angular/material/form-field","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/select","@angular/material/core","@angular/material/slide-toggle","@shared/components/hint-tooltip-icon.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/material/icon","@angular/material/tooltip","@shared/components/script-lang.component","@angular/cdk/keycodes","@angular/material/chips","@shared/pipe/safe.pipe","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/flex-layout/extended","@angular/material/list","@angular/cdk/drag-drop","rxjs/operators","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@home/components/public-api","tslib","rxjs","@shared/components/help-popup.component","@shared/components/entity/entity-subtype-list.component","@shared/components/relation/relation-type-autocomplete.component","@home/components/relation/relation-filters.component","@angular/material/expansion","@shared/components/file-input.component","@shared/components/button/toggle-password.component","@shared/components/string-items-list.component","@shared/components/toggle-header.component","@shared/components/toggle-select.component","@shared/components/entity/entity-list.component","@shared/components/notification/template-autocomplete.component","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/radio","@shared/components/slack-conversation-autocomplete.component","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component","@angular/cdk/platform"],(function(e){"use strict";var t,n,r,o,a,i,l,s,m,p,d,u,c,f,g,y,x,b,h,v,C,F,k,L,T,I,N,S,q,A,M,E,G,D,w,V,P,R,O,_,B,K,z,U,H,j,$,Q,J,Y,W,Z,X,ee,te,ne,re,oe,ae,ie,le,se,me,pe,de,ue,ce,fe,ge,ye,xe,be,he,ve,Ce,Fe,ke,Le,Te,Ie,Ne,Se,qe,Ae,Me,Ee,Ge,De,we,Ve,Pe,Re,Oe,_e,Be,Ke,ze,Ue,He,je,$e,Qe,Je,Ye,We,Ze,Xe,et,tt,nt,rt,ot,at,it,lt,st,mt,pt;return{setters:[function(e){t=e,n=e.Component,r=e.EventEmitter,o=e.ViewChild,a=e.forwardRef,i=e.Input,l=e.InjectionToken,s=e.Injectable,m=e.Inject,p=e.Optional,d=e.Directive,u=e.Output,c=e.NgModule},function(e){f=e.RuleNodeConfigurationComponent,g=e.AttributeScope,y=e.telemetryTypeTranslations,x=e.ScriptLanguage,b=e.AlarmSeverity,h=e.alarmSeverityTranslations,v=e.EntitySearchDirection,C=e.entitySearchDirectionTranslations,F=e.EntityType,k=e.entityFields,L=e.PageComponent,T=e.coerceBoolean,I=e.MessageType,N=e.messageTypeNames,S=e,q=e.AlarmStatus,A=e.alarmStatusTranslations,M=e.SharedModule,E=e.AggregationType,G=e.aggregationTranslations,D=e.NotificationType,w=e.SlackChanelType,V=e.SlackChanelTypesTranslateMap},function(e){P=e},function(e){R=e,O=e.Validators,_=e.NgControl,B=e.NG_VALUE_ACCESSOR,K=e.NG_VALIDATORS,z=e.FormArray,U=e.FormGroup},function(e){H=e,j=e.DOCUMENT,$=e.CommonModule},function(e){Q=e},function(e){J=e},function(e){Y=e},function(e){W=e},function(e){Z=e},function(e){X=e},function(e){ee=e},function(e){te=e},function(e){ne=e},function(e){re=e.getCurrentAuthState,oe=e,ae=e.isEqual,ie=e.isDefinedAndNotNull,le=e.deepTrim,se=e.isObject,me=e.isNotEmptyStr},function(e){pe=e},function(e){de=e},function(e){ue=e},function(e){ce=e},function(e){fe=e},function(e){ge=e.ENTER,ye=e.COMMA,xe=e.SEMICOLON},function(e){be=e},function(e){he=e},function(e){ve=e},function(e){Ce=e},function(e){Fe=e.coerceBooleanProperty,ke=e.coerceElement,Le=e.coerceNumberProperty},function(e){Te=e},function(e){Ie=e},function(e){Ne=e},function(e){Se=e},function(e){qe=e.tap,Ae=e.map,Me=e.startWith,Ee=e.mergeMap,Ge=e.share,De=e.takeUntil,we=e.auditTime},function(e){Ve=e},function(e){Pe=e},function(e){Re=e.HomeComponentsModule},function(e){Oe=e.__decorate},function(e){_e=e.Subject,Be=e.takeUntil,Ke=e.of,ze=e.EMPTY,Ue=e.fromEvent},function(e){He=e},function(e){je=e},function(e){$e=e},function(e){Qe=e},function(e){Je=e},function(e){Ye=e},function(e){We=e},function(e){Ze=e},function(e){Xe=e},function(e){et=e},function(e){tt=e},function(e){nt=e},function(e){rt=e},function(e){ot=e},function(e){at=e},function(e){it=e},function(e){lt=e},function(e){st=e},function(e){mt=e.normalizePassiveListenerOptions,pt=e}],execute:function(){class dt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dt,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
    ",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,decorators:[{type:n,args:[{selector:"tb-node-empty-config",template:"
    "}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ut extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ut,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,decorators:[{type:n,args:[{selector:"tb-action-node-assign-to-customer-config",template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ct extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]],updateAttributesOnlyOnValueChange:[!!e&&e.updateAttributesOnlyOnValueChange,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===g.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1}),this.attributesConfigForm.get("updateAttributesOnlyOnValueChange").patchValue(!1,{emitEvent:!1})}))}}e("AttributesConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ct,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,decorators:[{type:n,args:[{selector:"tb-action-node-attributes-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ft extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[O.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===x.JS?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===x.TBEL?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",o=this.clearAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.clearAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ft,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gt extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.alarmSeverities=Object.keys(b),this.alarmSeverityTranslationMap=h,this.separatorKeysCodes=[ge,ye,xe],this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(e){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,n=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;t?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([O.required]),this.createAlarmConfigForm.get("severity").setValidators([O.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==x.TBEL||this.tbelEnabled||(r=x.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const o=!1===t||!0===n;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(o&&r===x.JS?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===x.TBEL?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",o=this.createAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.createAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}removeKey(e,t){const n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!e||t){this.createAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gt,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
    \n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
    \n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,decorators:[{type:n,args:[{selector:"tb-action-node-create-alarm-config",template:'
    \n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
    \n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
    \n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class yt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[O.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==F.DEVICE&&t!==F.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([O.required,O.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yt,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
    tb.rulenode.create-entity-if-not-exists-hint
    \n
    \n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
    tb.rulenode.remove-current-relations-hint
    \n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
    tb.rulenode.change-originator-to-related-entity-hint
    \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,decorators:[{type:n,args:[{selector:"tb-action-node-create-relation-config",template:'
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
    tb.rulenode.create-entity-if-not-exists-hint
    \n
    \n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
    tb.rulenode.remove-current-relations-hint
    \n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
    tb.rulenode.change-originator-to-related-entity-hint
    \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,n=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([O.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
    tb.rulenode.delete-relation-hint
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,decorators:[{type:n,args:[{selector:"tb-action-node-delete-relation-config",template:'
    \n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
    tb.rulenode.delete-relation-hint
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,O.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,O.required]})}}e("DeviceProfileConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bt,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
    \n',dependencies:[{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,decorators:[{type:n,args:[{selector:"tb-device-profile-config",template:'
    \n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ht extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function"}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[O.required,O.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[O.required,O.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS),e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(e){const t=this.generatorConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",o=this.generatorConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.generatorConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}var vt;e("GeneratorConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ht,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
    \n \n \n \n
    \n\n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ce.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,decorators:[{type:n,args:[{selector:"tb-action-node-generator-config",template:'
    \n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
    \n \n \n \n
    \n\n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(vt||(vt={}));const Ct=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer"],[vt.TENANT,"tb.rulenode.originator-tenant"],[vt.RELATED,"tb.rulenode.originator-related"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[vt.ENTITY,"tb.rulenode.originator-entity"]]),Ft=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer-desc"],[vt.TENANT,"tb.rulenode.originator-tenant-desc"],[vt.RELATED,"tb.rulenode.originator-related-entity-desc"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator-desc"],[vt.ENTITY,"tb.rulenode.originator-entity-by-name-pattern-desc"]]),kt=[k.createdTime,k.name,{value:"type",name:"tb.rulenode.profile-name",keyName:"originatorProfileName"},k.firstName,k.lastName,k.email,k.title,k.country,k.state,k.city,k.address,k.address2,k.zip,k.phone,k.label,{value:"id",name:"tb.rulenode.id",keyName:"id"},{value:"additionalInfo",name:"tb.rulenode.additional-info",keyName:"additionalInfo"}],Lt=new Map([["type","profileName"],["createdTime","createdTime"],["name","name"],["firstName","firstName"],["lastName","lastName"],["email","email"],["title","title"],["country","country"],["state","state"],["city","city"],["address","address"],["address2","address2"],["zip","zip"],["phone","phone"],["label","label"],["id","id"],["additionalInfo","additionalInfo"]]);var Tt;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Tt||(Tt={}));const It=new Map([[Tt.CIRCLE,"tb.rulenode.perimeter-circle"],[Tt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Nt;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(Nt||(Nt={}));const St=new Map([[Nt.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Nt.SECONDS,"tb.rulenode.time-unit-seconds"],[Nt.MINUTES,"tb.rulenode.time-unit-minutes"],[Nt.HOURS,"tb.rulenode.time-unit-hours"],[Nt.DAYS,"tb.rulenode.time-unit-days"]]);var qt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(qt||(qt={}));const At=new Map([[qt.METER,"tb.rulenode.range-unit-meter"],[qt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[qt.FOOT,"tb.rulenode.range-unit-foot"],[qt.MILE,"tb.rulenode.range-unit-mile"],[qt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Mt;!function(e){e.ID="ID",e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Mt||(Mt={}));const Et=new Map([[Mt.ID,"tb.rulenode.entity-details-id"],[Mt.TITLE,"tb.rulenode.entity-details-title"],[Mt.COUNTRY,"tb.rulenode.entity-details-country"],[Mt.STATE,"tb.rulenode.entity-details-state"],[Mt.CITY,"tb.rulenode.entity-details-city"],[Mt.ZIP,"tb.rulenode.entity-details-zip"],[Mt.ADDRESS,"tb.rulenode.entity-details-address"],[Mt.ADDRESS2,"tb.rulenode.entity-details-address2"],[Mt.PHONE,"tb.rulenode.entity-details-phone"],[Mt.EMAIL,"tb.rulenode.entity-details-email"],[Mt.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Gt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Gt||(Gt={}));const Dt=new Map([[Gt.FIRST,"tb.rulenode.first"],[Gt.LAST,"tb.rulenode.last"],[Gt.ALL,"tb.rulenode.all"]]),wt=new Map([[Gt.FIRST,"tb.rulenode.first-mode-hint"],[Gt.LAST,"tb.rulenode.last-mode-hint"],[Gt.ALL,"tb.rulenode.all-mode-hint"]]);var Vt,Pt;!function(e){e.ASC="ASC",e.DESC="DESC"}(Vt||(Vt={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Pt||(Pt={}));const Rt=new Map([[Pt.ATTRIBUTES,"tb.rulenode.attributes"],[Pt.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Pt.FIELDS,"tb.rulenode.fields"]]),Ot=new Map([[Pt.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Pt.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Pt.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),_t=new Map([[Vt.ASC,"tb.rulenode.ascending"],[Vt.DESC,"tb.rulenode.descending"]]);var Bt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Bt||(Bt={}));const Kt=new Map([[Bt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Bt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),zt=["anonymous","basic","cert.PEM"],Ut=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Ht=["sas","cert.PEM"],jt=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var $t;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}($t||($t={}));const Qt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Jt=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var Yt;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(Yt||(Yt={}));const Wt=new Map([[Yt.CUSTOM,{value:Yt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Yt.ADD,{value:Yt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Yt.SUB,{value:Yt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Yt.MULT,{value:Yt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Yt.DIV,{value:Yt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Yt.SIN,{value:Yt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.SINH,{value:Yt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Yt.COS,{value:Yt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.COSH,{value:Yt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Yt.TAN,{value:Yt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Yt.TANH,{value:Yt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ACOS,{value:Yt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Yt.ASIN,{value:Yt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN,{value:Yt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN2,{value:Yt.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[Yt.EXP,{value:Yt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Yt.EXPM1,{value:Yt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Yt.SQRT,{value:Yt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Yt.CBRT,{value:Yt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Yt.GET_EXP,{value:Yt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Yt.HYPOT,{value:Yt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Yt.LOG,{value:Yt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG10,{value:Yt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG1P,{value:Yt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Yt.CEIL,{value:Yt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR,{value:Yt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR_DIV,{value:Yt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Yt.FLOOR_MOD,{value:Yt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Yt.ABS,{value:Yt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Yt.MIN,{value:Yt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Yt.MAX,{value:Yt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Yt.POW,{value:Yt.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[Yt.SIGNUM,{value:Yt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Yt.RAD,{value:Yt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Yt.DEG,{value:Yt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Zt,Xt,en;!function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT"}(Zt||(Zt={})),function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES"}(Xt||(Xt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(en||(en={}));const tn=new Map([[en.DATA,"tb.rulenode.message-to-metadata"],[en.METADATA,"tb.rulenode.metadata-to-message"]]),nn=(new Map([[en.DATA,"tb.rulenode.from-message"],[en.METADATA,"tb.rulenode.from-metadata"]]),new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.metadata"]])),rn=new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.message-metadata"]]),on=new Map([[Zt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Fetch argument value from incoming message"}],[Zt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Fetch argument value from incoming message metadata"}],[Zt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Fetch attribute value from database"}],[Zt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Fetch latest time-series value from database"}],[Zt.CONSTANT,{name:"tb.rulenode.constant-type",description:"Define constant value"}]]),an=new Map([[Xt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Add result to the outgoing message"}],[Xt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Add result to the outgoing message metadata"}],[Xt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Store result as an entity attribute in the database"}],[Xt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Store result as an entity time-series in the database"}]]),ln=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var sn,mn;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(sn||(sn={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(mn||(mn={}));const pn=new Map([[sn.SHARED_SCOPE,"tb.rulenode.shared-scope"],[sn.SERVER_SCOPE,"tb.rulenode.server-scope"],[sn.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);class dn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.keys(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.keys(qt),this.rangeUnitTranslationMap=At,this.timeUnits=Object.keys(Nt),this.timeUnitsTranslationMap=St}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[O.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[O.required]],perimeterType:[e?e.perimeterType:null,[O.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[O.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[O.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoActionConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([O.required])),t||n!==Tt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",dn),dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dn,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
    \n
    \n
    \n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dn,decorators:[{type:n,args:[{selector:"tb-action-node-gps-geofencing-config",template:'
    \n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
    \n
    \n
    \n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class un extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-to-string-function"}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",o=this.logConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.logConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.logConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",un),un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:un,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:un,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:un,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class cn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[O.required,O.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[O.required]]})}}e("MsgCountConfigComponent",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cn,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-count-config",template:'
    \n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([O.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([O.required,O.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fn,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
    tb.rulenode.use-metadata-period-in-seconds-patterns-hint
    \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-delay-config",template:'
    \n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
    tb.rulenode.use-metadata-period-in-seconds-patterns-hint
    \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class gn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToCloudConfigComponent",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gn,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-cloud-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class yn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToEdgeConfigComponent",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yn,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-edge-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xn,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.request-id-metadata-attribute\n \n \n
    \n',dependencies:[{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-reply-config",template:'
    \n \n tb.rulenode.request-id-metadata-attribute\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[O.required,O.min(0)]]})}}e("RpcRequestConfigComponent",bn),bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bn,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-request-config",template:'
    \n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class hn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const n of Object.keys(e))Object.prototype.hasOwnProperty.call(e,n)&&t.push(this.fb.group({key:[n,[O.required]],value:[e[n],[O.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[O.required]],value:["",[O.required]]}))}validate(e){const t=this.kvListFormGroup.get("keyVals").value;if(!t.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const e of t)if(e.key===e.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigOldComponent",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hn,selector:"tb-kv-map-config-old",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:B,useExisting:a((()=>hn)),multi:!0},{provide:K,useExisting:a((()=>hn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n {{ keyText | translate }}\n {{ valText | translate }}\n \n
    \n
    \n
    \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Te.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Ie.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:B,useExisting:a((()=>hn)),multi:!0},{provide:K,useExisting:a((()=>hn)),multi:!0}],template:'
    \n
    \n {{ keyText | translate }}\n {{ valText | translate }}\n \n
    \n
    \n
    \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],required:[{type:i}]}});class vn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[O.required,O.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[O.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vn,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,decorators:[{type:n,args:[{selector:"tb-action-node-custom-table-config",template:'
    \n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[O.required,O.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cn,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
    tb.rulenode.use-server-ts-hint
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,decorators:[{type:n,args:[{selector:"tb-action-node-timeseries-config",template:'
    \n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
    tb.rulenode.use-server-ts-hint
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Fn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fn,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,decorators:[{type:n,args:[{selector:"tb-action-node-un-assign-to-customer-config",template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class kn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],keys:[e?e.keys:null,[O.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}removeKey(e){const t=this.deleteAttributesConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}addKey(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.deleteAttributesConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteAttributesConfigComponent",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kn,selector:"tb-action-node-delete-attributes-config",viewQueries:[{propertyName:"attributeChipList",first:!0,predicate:["attributeChipList"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
    tb.rulenode.send-attributes-deleted-notification-hint
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    tb.rulenode.notify-device-on-delete-hint
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-action-node-delete-attributes-config",template:'
    \n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
    tb.rulenode.send-attributes-deleted-notification-hint
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    tb.rulenode.notify-device-on-delete-hint
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:o,args:["attributeChipList"]}]}});class Ln extends L{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t){super(e),this.store=e,this.fb=t,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Wt,this.ArgumentType=Zt,this.attributeScopeMap=pn,this.argumentTypeMap=on,this.arguments=Object.values(Zt),this.attributeScope=Object.values(sn),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.argumentsFormGroup=this.fb.group({arguments:this.fb.array([])}),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray,n=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,n),this.updateArgumentNames()}get argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):(this.argumentsFormGroup.enable({emitEvent:!1}),this.argumentsFormArray.controls.forEach((e=>this.updateArgumentControlValidators(e))))}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){const t=[];e&&e.forEach(((e,n)=>{t.push(this.createArgumentControl(e,n))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t),{emitEvent:!1}),this.setupArgumentsFormGroup()}removeArgument(e){this.argumentsFormArray.removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormArray,n=this.createArgumentControl(null,t.length);t.push(n,{emitEvent:e})}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(e=!1){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===Yt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([O.minLength(this.minArgs),O.maxLength(this.maxArgs)]);this.argumentsFormArray.length>this.maxArgs;)this.removeArgument(this.maxArgs-1);for(;this.argumentsFormArray.length{this.updateArgumentControlValidators(n),n.get("attributeScope").updateValueAndValidity({emitEvent:!1}),n.get("defaultValue").updateValueAndValidity({emitEvent:!1})}))),n}updateArgumentControlValidators(e){const t=e.get("type").value;t===Zt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Zt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormArray.controls.forEach(((e,t)=>{e.get("name").setValue(ln[t])}))}updateModel(){const e=this.argumentsFormArray.value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ln,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:B,useExisting:a((()=>Ln)),multi:!0},{provide:K,useExisting:a((()=>Ln)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n\n
    \n \n \n
    \n \n
    \n {{argumentControl.get(\'name\').value}}.\n
    \n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
    \n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n tb.rulenode.no-arguments-prompt\n
    \n \n
    \n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Ne.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:Ne.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:Se.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","cdkDropListData","cdkDropListOrientation","id","cdkDropListLockAxis","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListAutoScrollDisabled","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{kind:"directive",type:Se.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragData","cdkDragLockAxis","cdkDragRootElement","cdkDragBoundary","cdkDragStartDelay","cdkDragFreeDragPosition","cdkDragDisabled","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragPreviewContainer"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{kind:"directive",type:Se.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Ie.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:B,useExisting:a((()=>Ln)),multi:!0},{provide:K,useExisting:a((()=>Ln)),multi:!0}],template:'
    \n\n
    \n \n \n
    \n \n
    \n {{argumentControl.get(\'name\').value}}.\n
    \n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
    \n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n tb.rulenode.no-arguments-prompt\n
    \n \n
    \n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],function:[{type:i}]}});class Tn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...Wt.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(qe((e=>{let t;t="string"==typeof e&&Yt[e]?Yt[e]:null,this.updateView(t)})),Ae((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=Wt.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}e("MathFunctionAutocompleteComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tn,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Tn)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Ve.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ve.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Pe.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:B,useExisting:a((()=>Tn)),multi:!0}],template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.UntypedFormBuilder}]},propDecorators:{required:[{type:i}],disabled:[{type:i}],operationInput:[{type:o,args:["operationInput",{static:!0}]}]}});class In extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Yt,this.ArgumentTypeResult=Xt,this.argumentTypeResultMap=an,this.attributeScopeMap=pn,this.argumentsResult=Object.values(Xt),this.attributeScopeResult=Object.values(mn)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[O.required]],arguments:[e?e.arguments:null,[O.required]],customFunction:[e?e.customFunction:"",[O.required]],result:this.fb.group({type:[e?e.result.type:null,[O.required]],attributeScope:[e?e.result.attributeScope:null,[O.required]],key:[e?e.result.key:"",[O.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,n=this.mathFunctionConfigForm.get("result.type").value;t===Yt.CUSTOM?(this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}),null===this.mathFunctionConfigForm.get("customFunction").value&&this.mathFunctionConfigForm.get("customFunction").patchValue("(x - 32) / 1.8",{emitEvent:!1})):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Xt.ATTRIBUTE?this.mathFunctionConfigForm.get("result.attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result.attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result.attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}}e("MathFunctionConfigComponent",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:In,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n tb.rulenode.argument-tile\n \n \n
    \n
    \n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
    \n
    \n tb.rulenode.result-title\n
    \n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
    \n
    \n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
    \n
    \n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
    \n
    \n
    \n
    \n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Ln,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:Tn,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-action-node-math-function-config",template:'
    \n \n \n
    \n tb.rulenode.argument-tile\n \n \n
    \n
    \n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
    \n
    \n tb.rulenode.result-title\n
    \n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
    \n
    \n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
    \n
    \n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
    \n
    \n
    \n
    \n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Nn{constructor(){this.textAlign="left"}}e("ExampleHintComponent",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,deps:[],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Nn,selector:"tb-example-hint",inputs:{hintText:"hintText",popupHelpLink:"popupHelpLink",textAlign:"textAlign"},ngImport:t,template:'
    \n
    \n
    \n
    \n
    \n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:He.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-example-hint",template:'
    \n
    \n
    \n
    \n
    \n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"]}]}],propDecorators:{hintText:[{type:i}],popupHelpLink:[{type:i}],textAlign:[{type:i}]}});class Sn{constructor(e,t){this.injector=e,this.fb=t,this.propagateChange=()=>{},this.destroy$=new _e,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1,this.duplicateValuesValidator=e=>e.controls.key.value===e.controls.value.value&&e.controls.key.value&&e.controls.value.value?{uniqueKeyValuePair:!0}:null,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.kvListFormGroup&&this.kvListFormGroup.get("keyVals")&&"VALID"===this.kvListFormGroup.get("keyVals")?.status)return null;const t={};if(this.kvListFormGroup&&this.kvListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ae(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.kvListFormGroup.valueChanges.pipe(Be(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))})),this.kvListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1})}}removeKeyVal(e){this.keyValsFormArray().removeAt(e)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))}validate(){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,deps:[{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sn,selector:"tb-kv-map-config",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",labelText:"labelText",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>Sn)),multi:!0},{provide:K,useExisting:a((()=>Sn)),multi:!0}],ngImport:t,template:'
    \n
    \n
    {{ labelText }}
    \n
    \n {{ requiredText }}\n
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
    \n
    \n
    \n
    \n
    \n
    {{ keyText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Oe([T()],Sn.prototype,"disabled",void 0),Oe([T()],Sn.prototype,"uniqueKeyValuePairValidator",void 0),Oe([T()],Sn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:B,useExisting:a((()=>Sn)),multi:!0},{provide:K,useExisting:a((()=>Sn)),multi:!0}],template:'
    \n
    \n
    {{ labelText }}
    \n
    \n {{ requiredText }}\n
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
    \n
    \n
    \n
    \n
    \n
    {{ keyText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class qn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.entityType=F,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],relationType:[null],deviceTypes:[null,[O.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qn,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>qn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n \n \n help\n \n
    \n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:je.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","floatLabel","label","disabled","entityType","emptyInputPlaceholder","filledInputPlaceholder","appearance","subscriptSizing","additionalClasses"]},{kind:"component",type:$e.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:B,useExisting:a((()=>qn)),multi:!0}],template:'
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n \n \n help\n \n
    \n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class An extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),An.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:An,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>An)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.relations-query
    \n
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n
    \n
    \n
    relation.relation-filters
    \n \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Qe.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:B,useExisting:a((()=>An)),multi:!0}],template:'
    \n
    tb.rulenode.relations-query
    \n
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n
    \n
    \n
    relation.relation-filters
    \n \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Mn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.add-message-type",this.separatorKeysCodes=[ge,ye,xe],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(I))this.messageTypesList.push({name:N.get(I[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(Me(""),Ae((e=>e||"")),Ee((e=>this.fetchMessageTypes(e))),Ge())}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return e&&e.length>0}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ke(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ke(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t;const n=e.trim(),r=this.messageTypesList.find((e=>e.name===n));t=r?{name:r.name,value:r.value}:{name:n,value:n},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,deps:[{token:P.Store},{token:Z.TranslateService},{token:S.TruncatePipe},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Mn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
    \n
    \n tb.rulenode.no-message-types-found\n
    \n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
    \n
    \n
    \n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Ve.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Ve.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Ve.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Pe.HighlightPipe,name:"highlight"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0}],template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
    \n
    \n tb.rulenode.no-message-types-found\n
    \n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
    \n
    \n
    \n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:S.TruncatePipe},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],label:[{type:i}],placeholder:[{type:i}],disabled:[{type:i}],chipList:[{type:o,args:["chipList",{static:!1}]}],matAutocomplete:[{type:o,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:o,args:["messageTypeInput",{static:!1}]}]}});class En extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=zt,this.credentialsTypeTranslationsMap=Ut,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[O.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const n=e[t];if(!n.firstChange&&n.currentValue!==n.previousValue&&n.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){ie(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators())}setDisabledState(e){e?this.credentialsConfigFormGroup.disable({emitEvent:!1}):(this.credentialsConfigFormGroup.enable({emitEvent:!1}),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([O.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[O.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(O.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return n=>{t||(t=[Object.keys(n.controls)]);return n?.controls&&t.some((t=>t.every((t=>!e(n.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:En,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:B,useExisting:a((()=>En)),multi:!0},{provide:K,useExisting:a((()=>En)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
    \n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
    {{ \'tb.rulenode.credentials-pem-hint\' | translate }}
    \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
    \n
    \n
    \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Je.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Je.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Je.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:Je.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:Je.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ye.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:We.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:B,useExisting:a((()=>En)),multi:!0},{provide:K,useExisting:a((()=>En)),multi:!0}],template:'
    \n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
    {{ \'tb.rulenode.credentials-pem-hint\' | translate }}
    \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
    \n
    \n
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],disableCertPemCredentials:[{type:i}],passwordFieldRequired:[{type:i}]}});const Gn=new l("WindowToken","undefined"!=typeof window&&window.document?{providedIn:"root",factory:()=>window}:{providedIn:"root",factory:()=>{}});class Dn{constructor(e,t,n){this.ngZone=e,this.document=t,this.window=n,this.copySubject=new _e,this.copyResponse$=this.copySubject.asObservable(),this.config={}}configure(e){this.config=e}copy(e){if(!this.isSupported||!e)return this.pushCopyResponse({isSuccess:!1,content:e});const t=this.copyFromContent(e);return t?this.pushCopyResponse({content:e,isSuccess:t}):this.pushCopyResponse({isSuccess:!1,content:e})}get isSupported(){return!!this.document.queryCommandSupported&&!!this.document.queryCommandSupported("copy")&&!!this.window}isTargetValid(e){if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){if(e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');return!0}throw new Error("Target should be input or textarea")}copyFromInputElement(e,t=!0){try{this.selectTarget(e);const n=this.copyText();return this.clearSelection(t?e:void 0,this.window),n&&this.isCopySuccessInIE11()}catch(e){return!1}}isCopySuccessInIE11(){const e=this.window.clipboardData;return!(e&&e.getData&&!e.getData("Text"))}copyFromContent(e,t=this.document.body){if(this.tempTextArea&&!t.contains(this.tempTextArea)&&this.destroy(this.tempTextArea.parentElement||void 0),!this.tempTextArea){this.tempTextArea=this.createTempTextArea(this.document,this.window);try{t.appendChild(this.tempTextArea)}catch(e){throw new Error("Container should be a Dom element")}}this.tempTextArea.value=e;const n=this.copyFromInputElement(this.tempTextArea,!1);return this.config.cleanUpAfterCopy&&this.destroy(this.tempTextArea.parentElement||void 0),n}destroy(e=this.document.body){this.tempTextArea&&(e.removeChild(this.tempTextArea),this.tempTextArea=void 0)}selectTarget(e){return e.select(),e.setSelectionRange(0,e.value.length),e.value.length}copyText(){return this.document.execCommand("copy")}clearSelection(e,t){e&&e.focus(),t.getSelection()?.removeAllRanges()}createTempTextArea(e,t){const n="rtl"===e.documentElement.getAttribute("dir");let r;r=e.createElement("textarea"),r.style.fontSize="12pt",r.style.border="0",r.style.padding="0",r.style.margin="0",r.style.position="absolute",r.style[n?"right":"left"]="-9999px";const o=t.pageYOffset||e.documentElement.scrollTop;return r.style.top=o+"px",r.setAttribute("readonly",""),r}pushCopyResponse(e){this.copySubject.observers.length>0&&this.ngZone.run((()=>{this.copySubject.next(e)}))}pushCopyReponse(e){this.pushCopyResponse(e)}}Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Dn,deps:[{token:t.NgZone},{token:j},{token:Gn,optional:!0}],target:t.ɵɵFactoryTarget.Injectable}),Dn.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Dn,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Dn,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:void 0,decorators:[{type:m,args:[j]}]},{type:void 0,decorators:[{type:p},{type:m,args:[Gn]}]}]}});class wn{constructor(e,t,n,o){this.ngZone=e,this.host=t,this.renderer=n,this.clipboardSrv=o,this.cbOnSuccess=new r,this.cbOnError=new r,this.onClick=e=>{this.clipboardSrv.isSupported?this.targetElm&&this.clipboardSrv.isTargetValid(this.targetElm)?this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm),this.targetElm.value,e):this.cbContent&&this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent,this.container),this.cbContent,e):this.handleResult(!1,void 0,e)}}ngOnInit(){this.ngZone.runOutsideAngular((()=>{this.clickListener=this.renderer.listen(this.host.nativeElement,"click",this.onClick)}))}ngOnDestroy(){this.clickListener&&this.clickListener(),this.clipboardSrv.destroy(this.container)}handleResult(e,t,n){let r={isSuccess:e,content:t,successMessage:this.cbSuccessMsg,event:n};e?this.cbOnSuccess.observed&&this.ngZone.run((()=>{this.cbOnSuccess.emit(r)})):this.cbOnError.observed&&this.ngZone.run((()=>{this.cbOnError.emit(r)})),this.clipboardSrv.pushCopyResponse(r)}}wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:wn,deps:[{token:t.NgZone},{token:t.ElementRef},{token:t.Renderer2},{token:Dn}],target:t.ɵɵFactoryTarget.Directive}),wn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:wn,selector:"[ngxClipboard]",inputs:{targetElm:["ngxClipboard","targetElm"],container:"container",cbContent:"cbContent",cbSuccessMsg:"cbSuccessMsg"},outputs:{cbOnSuccess:"cbOnSuccess",cbOnError:"cbOnError"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:wn,decorators:[{type:d,args:[{selector:"[ngxClipboard]"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:t.ElementRef},{type:t.Renderer2},{type:Dn}]},propDecorators:{targetElm:[{type:i,args:["ngxClipboard"]}],container:[{type:i}],cbContent:[{type:i}],cbSuccessMsg:[{type:i}],cbOnSuccess:[{type:u}],cbOnError:[{type:u}]}});class Vn{constructor(e,t,n){this._clipboardService=e,this._viewContainerRef=t,this._templateRef=n}ngOnInit(){this._clipboardService.isSupported&&this._viewContainerRef.createEmbeddedView(this._templateRef)}}Vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Vn,deps:[{token:Dn},{token:t.ViewContainerRef},{token:t.TemplateRef}],target:t.ɵɵFactoryTarget.Directive}),Vn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:Vn,selector:"[ngxClipboardIfSupported]",ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Vn,decorators:[{type:d,args:[{selector:"[ngxClipboardIfSupported]"}]}],ctorParameters:function(){return[{type:Dn},{type:t.ViewContainerRef},{type:t.TemplateRef}]}});class Pn{}Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Pn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,declarations:[wn,Vn],imports:[$],exports:[wn,Vn]}),Pn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,imports:[[$]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,decorators:[{type:c,args:[{imports:[$],declarations:[wn,Vn],exports:[wn,Vn]}]}]});class Rn{set required(e){this.requiredValue!==e&&(this.requiredValue=e,this.updateValidators())}get required(){return this.requiredValue}constructor(e){this.fb=e,this.subscriptSizing="fixed",this.messageTypes=[{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},{name:"Custom",value:""}],this.propagateChange=()=>{},this.destroy$=new _e,this.messageTypeFormGroup=this.fb.group({messageTypeAlias:[null,[O.required]],messageType:[{value:null,disabled:!0},[O.maxLength(255)]]}),this.messageTypeFormGroup.get("messageTypeAlias").valueChanges.pipe(Be(this.destroy$)).subscribe((e=>this.updateMessageTypeValue(e))),this.messageTypeFormGroup.valueChanges.pipe(Be(this.destroy$)).subscribe((()=>this.updateView()))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}registerOnTouched(e){}registerOnChange(e){this.propagateChange=e}writeValue(e){this.modelValue=e;let t=this.messageTypes.find((t=>t.value===e));t||(t=this.messageTypes.find((e=>""===e.value))),this.messageTypeFormGroup.get("messageTypeAlias").patchValue(t,{emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1})}validate(){return this.messageTypeFormGroup.valid?null:{messageTypeInvalid:!0}}setDisabledState(e){this.disabled=e,e?this.messageTypeFormGroup.disable({emitEvent:!1}):(this.messageTypeFormGroup.enable({emitEvent:!1}),"Custom"!==this.messageTypeFormGroup.get("messageTypeAlias").value?.name&&this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}))}updateView(){const e=this.messageTypeFormGroup.getRawValue().messageType;this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}updateValidators(){this.messageTypeFormGroup.get("messageType").setValidators(this.required?[O.required,O.maxLength(255)]:[O.maxLength(255)]),this.messageTypeFormGroup.get("messageType").updateValueAndValidity({emitEvent:!1})}updateMessageTypeValue(e){"Custom"!==e?.name?this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}):this.messageTypeFormGroup.get("messageType").enable({emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e.value??null)}}e("OutputMessageTypeAutocompleteComponent",Rn),Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rn,deps:[{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Rn,selector:"tb-output-message-type-autocomplete",inputs:{subscriptSizing:"subscriptSizing",disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Rn)),multi:!0},{provide:K,useExisting:a((()=>Rn)),multi:!0}],ngImport:t,template:'
    \n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
    \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:wn,selector:"[ngxClipboard]",inputs:["ngxClipboard","container","cbContent","cbSuccessMsg"],outputs:["cbOnSuccess","cbOnError"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Oe([T()],Rn.prototype,"disabled",void 0),Oe([T()],Rn.prototype,"required",null),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rn,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:B,useExisting:a((()=>Rn)),multi:!0},{provide:K,useExisting:a((()=>Rn)),multi:!0}],template:'
    \n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
    \n\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder}]},propDecorators:{subscriptSizing:[{type:i}],disabled:[{type:i}],required:[{type:i}]}});class On{constructor(e,t){this.fb=e,this.translate=t,this.translation=nn,this.propagateChange=()=>{},this.destroy$=new _e,this.selectOptions=[]}ngOnInit(){this.initOptions(),this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(De(this.destroy$)).subscribe((e=>{e&&this.propagateChange(e)}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}initOptions(){for(const e of this.translation.keys())this.selectOptions.push({value:e,name:this.translate.instant(this.translation.get(e))})}writeValue(e){this.chipControlGroup.get("chipControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.chipControlGroup.disable({emitEvent:!1}):this.chipControlGroup.enable({emitEvent:!1})}}e("MsgMetadataChipComponent",On),On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:On,deps:[{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),On.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:On,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText",translation:"translation"},providers:[{provide:B,useExisting:a((()=>On)),multi:!0}],ngImport:t,template:'
    \n
    {{ labelText }}
    \n \n {{ option.name }}\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:On,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:B,useExisting:a((()=>On)),multi:!0}],template:'
    \n
    {{ labelText }}
    \n \n {{ option.name }}\n \n
    \n'}]}],ctorParameters:function(){return[{type:R.FormBuilder},{type:Z.TranslateService}]},propDecorators:{labelText:[{type:i}],translation:[{type:i}]}});class _n extends L{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new _e,this.sourceFieldSubcritption=[],this.propagateChange=null,this.disabled=!1,this.required=!1,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.svListFormGroup&&this.svListFormGroup.get("keyVals")&&"VALID"===this.svListFormGroup.get("keyVals")?.status)return null;const t={};if(this.svListFormGroup&&this.svListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ae(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.svListFormGroup.valueChanges.pipe(De(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.svListFormGroup.disable({emitEvent:!1}):this.svListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))})),this.svListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1});for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e)}}filterSelectOptions(e){const t=[];for(const e of this.svListFormGroup.get("keyVals").value){const n=this.selectOptions.find((t=>t.value===e.key));n&&t.push(n)}const n=[];for(const r of this.selectOptions)ie(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.keyValsFormArray().removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length-1))}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(De(this.destroy$)).subscribe((t=>{const n=Lt.get(t);e.get("value").patchValue(this.targetKeyPrefix+n[0].toUpperCase()+n.slice(1))})))}validate(e){return!this.svListFormGroup.get("keyVals").value.length&&this.required?{svMapRequired:!0}:this.svListFormGroup.valid?null:{svFieldsRequired:!0}}updateModel(){const e=this.svListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.svListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("SvMapConfigComponent",_n),_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_n,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:_n,selector:"tb-sv-map-config",inputs:{selectOptions:"selectOptions",disabled:"disabled",labelText:"labelText",requiredText:"requiredText",targetKeyPrefix:"targetKeyPrefix",selectText:"selectText",selectRequiredText:"selectRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>_n)),multi:!0},{provide:K,useExisting:a((()=>_n)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    {{ labelText }}
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ requiredText }}\n
    \n
    \n
    \n
    \n
    \n
    {{ selectText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n {{option.name}}\n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Ie.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Oe([T()],_n.prototype,"disabled",void 0),Oe([T()],_n.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_n,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:B,useExisting:a((()=>_n)),multi:!0},{provide:K,useExisting:a((()=>_n)),multi:!0}],template:'
    \n
    \n
    {{ labelText }}
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ requiredText }}\n
    \n
    \n
    \n
    \n
    \n
    {{ selectText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n {{option.name}}\n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{selectOptions:[{type:i}],disabled:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],targetKeyPrefix:[{type:i}],selectText:[{type:i}],selectRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class Bn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Fe(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigOldComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Bn,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
    \n
    relation.relation-filters
    \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Qe.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0}],template:'
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
    \n
    relation.relation-filters
    \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Kn{constructor(e,t){this.translate=e,this.fb=t,this.propagateChange=e=>{},this.destroy$=new _e,this.separatorKeysCodes=[ge,ye,xe],this.onTouched=()=>{}}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[[],[]],sharedAttributeNames:[[],[]],serverAttributeNames:[[],[]],latestTsKeyNames:[[],[]],getLatestValueWithTs:[!1,[]]},{validators:this.atLeastOne(O.required,["clientAttributeNames","sharedAttributeNames","serverAttributeNames","latestTsKeyNames"])}),this.attributeControlGroup.valueChanges.pipe(De(this.destroy$)).subscribe((e=>{this.propagateChange(this.preparePropagateValue(e))}))}preparePropagateValue(e){const t={};for(const n in e)t[n]="getLatestValueWithTs"===n||ie(e[n])?e[n]:[];return t}validate(){return this.attributeControlGroup.valid?null:{atLeastOneRequired:!0}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}writeValue(e){this.attributeControlGroup.setValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SelectAttributesComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,deps:[{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kn,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0},{provide:K,useExisting:Kn,multi:!0}],ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
    \n
    \n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgTemplateOutlet,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0},{provide:K,useExisting:Kn,multi:!0}],template:'
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
    \n
    \n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:Z.TranslateService},{type:R.FormBuilder}]},propDecorators:{popupHelpLink:[{type:i}]}});class zn extends L{constructor(e,t){super(e),this.store=e,this.fb=t,this.propagateChange=null,this.destroy$=new _e,this.alarmStatus=q,this.alarmStatusTranslations=A}ngOnInit(){this.alarmStatusGroup=this.fb.group({alarmStatus:[null,[]]}),this.alarmStatusGroup.get("alarmStatus").valueChanges.pipe(De(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}setDisabledState(e){e?this.alarmStatusGroup.disable({emitEvent:!1}):this.alarmStatusGroup.enable({emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}writeValue(e){this.alarmStatusGroup.get("alarmStatus").patchValue(e,{emitEvent:!1})}}e("AlarmStatusSelectComponent",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zn,selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>zn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
    \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
    \n
    \n
    \n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"],dependencies:[{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,decorators:[{type:n,args:[{selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>zn)),multi:!0}],template:'
    \n \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
    \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
    \n
    \n
    \n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Un{}e("RulenodeCoreConfigCommonModule",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Un.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Un,declarations:[Sn,qn,An,Mn,En,Ln,Tn,Rn,hn,On,_n,Bn,Kn,zn,Nn],imports:[$,M,Re],exports:[Sn,qn,An,Mn,En,Ln,Tn,Rn,hn,On,_n,Bn,Kn,zn,Nn]}),Un.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,imports:[$,M,Re]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,decorators:[{type:c,args:[{declarations:[Sn,qn,An,Mn,En,Ln,Tn,Rn,hn,On,_n,Bn,Kn,zn,Nn],imports:[$,M,Re],exports:[Sn,qn,An,Mn,En,Ln,Tn,Rn,hn,On,_n,Bn,Kn,zn,Nn]}]}]});class Hn{}e("RuleNodeCoreConfigActionModule",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Hn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Hn,declarations:[kn,ct,Cn,bn,un,ut,ft,gt,yt,fn,xt,ht,dn,cn,xn,vn,Fn,bt,yn,gn,In],imports:[$,M,Re,Un],exports:[kn,ct,Cn,bn,un,ut,ft,gt,yt,fn,xt,ht,dn,cn,xn,vn,Fn,bt,yn,gn,In]}),Hn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,imports:[$,M,Re,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,decorators:[{type:c,args:[{declarations:[kn,ct,Cn,bn,un,ut,ft,gt,yt,fn,xt,ht,dn,cn,xn,vn,Fn,bt,yn,gn,In],imports:[$,M,Re,Un],exports:[kn,ct,Cn,bn,un,ut,ft,gt,yt,fn,xt,ht,dn,cn,xn,vn,Fn,bt,yn,gn,In]}]}]});class jn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[O.min(0),O.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:ie(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:ie(e?.outputValueKey)?e.outputValueKey:null,useCache:!ie(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!ie(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:ie(e?.periodValueKey)?e.periodValueKey:null,round:ie(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!ie(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return le(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([O.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:jn,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:"
    \n
    \n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
    \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
    \n
    \n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
    \n
    \n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
    \n
    \n
    \n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
    \n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
    \n
    \n
    \n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-calculate-delta-config",template:"
    \n
    \n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
    \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
    \n
    \n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
    \n
    \n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
    \n
    \n
    \n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
    \n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
    \n
    \n
    \n"}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class $n extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.customerAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,le(e)}prepareInputConfig(e){let t,n;return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),$n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:$n,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.mapping-of-customers
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
    \n
    tb.rulenode.mapping-of-customers
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Qn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e.deviceRelationsQuery,[O.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ie(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ie(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ie(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ie(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:ie(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!ie(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA,attributesControl:e?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("DeviceAttributesConfigComponent",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Qn,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.device-relations-query
    \n \n \n
    \n
    \n
    \n
    tb.rulenode.related-device-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:qn,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Kn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
    \n
    \n
    tb.rulenode.device-relations-query
    \n \n \n
    \n
    \n
    \n
    tb.rulenode.related-device-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Jn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.predefinedValues=[];for(const e of Object.keys(Mt))this.predefinedValues.push({value:Mt[e],name:this.translate.instant(Et.get(Mt[e]))})}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return t=ie(e?.addToMetadata)?e.addToMetadata?en.METADATA:en.DATA:e?.fetchTo?e.fetchTo:en.DATA,{detailsList:ie(e?.detailsList)?e.detailsList:null,fetchTo:t}}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("EntityDetailsConfigComponent",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Jn,selector:"tb-enrichment-node-entity-details-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n help\n \n \n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
    \n \n \n help\n \n \n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Yn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe],this.aggregationTypes=E,this.aggregations=Object.values(E),this.aggregationTypesTranslations=G,this.fetchMode=Gt,this.samplingOrders=Object.values(Vt),this.samplingOrdersTranslate=_t,this.timeUnits=Object.values(Nt),this.timeUnitsTranslationMap=St,this.deduplicationStrategiesHintTranslations=wt,this.headerOptions=[],this.timeUnitMap={[Nt.MILLISECONDS]:1,[Nt.SECONDS]:1e3,[Nt.MINUTES]:6e4,[Nt.HOURS]:36e5,[Nt.DAYS]:864e5},this.intervalValidator=()=>e=>e.get("startInterval").value*this.timeUnitMap[e.get("startIntervalTimeUnit").value]<=e.get("endInterval").value*this.timeUnitMap[e.get("endIntervalTimeUnit").value]?{intervalError:!0}:null;for(const e of Dt.keys())this.headerOptions.push({value:e,name:this.translate.instant(Dt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[O.required]],aggregation:[e.aggregation,[O.required]],fetchMode:[e.fetchMode,[O.required]],orderBy:[e.orderBy,[]],limit:[e.limit,[]],useMetadataIntervalPatterns:[e.useMetadataIntervalPatterns,[]],interval:this.fb.group({startInterval:[e.interval.startInterval,[]],startIntervalTimeUnit:[e.interval.startIntervalTimeUnit,[]],endInterval:[e.interval.endInterval,[]],endIntervalTimeUnit:[e.interval.endIntervalTimeUnit,[]]}),startIntervalPattern:[e.startIntervalPattern,[]],endIntervalPattern:[e.endIntervalPattern,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}toggleChange(e){this.getTelemetryFromDatabaseConfigForm.get("fetchMode").patchValue(e,{emitEvent:!0})}prepareOutputConfig(e){return e.startInterval=e.interval.startInterval,e.startIntervalTimeUnit=e.interval.startIntervalTimeUnit,e.endInterval=e.interval.endInterval,e.endIntervalTimeUnit=e.interval.endIntervalTimeUnit,delete e.interval,le(e)}prepareInputConfig(e){return se(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:ie(e?.aggregation)?e.aggregation:E.NONE,fetchMode:ie(e?.fetchMode)?e.fetchMode:Gt.FIRST,orderBy:ie(e?.orderBy)?e.orderBy:Vt.ASC,limit:ie(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!ie(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:ie(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:ie(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:Nt.MINUTES,endInterval:ie(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:ie(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:Nt.MINUTES},startIntervalPattern:ie(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:ie(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Gt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([O.required,O.min(2),O.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([this.intervalValidator()]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))}clearChipGrid(){this.getTelemetryFromDatabaseConfigForm.get("latestTsKeyNames").patchValue([],{emitEvent:!0})}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}defaultPaddingEnable(){return this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===Gt.ALL&&this.getTelemetryFromDatabaseConfigForm.get("aggregation").value===E.NONE}}e("GetTelemetryFromDatabaseConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Yn,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
    \n \n
    \n help\n \n
    \n
    \n
    tb.rulenode.fetch-interval
    \n
    \n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
    \n
    \n
    \n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n error_outline\n
    \n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
    \n
    \n
    \n \n
    \n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
    \n
    \n
    \n
    \n
    tb.rulenode.fetch-strategy
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
    \n
    \n
    \n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
    \n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
    \n
    \n
    \n
    \n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:He.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
    \n \n
    \n help\n \n
    \n
    \n
    tb.rulenode.fetch-interval
    \n
    \n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
    \n
    \n
    \n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n error_outline\n
    \n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
    \n
    \n
    \n \n
    \n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
    \n
    \n
    \n
    \n
    tb.rulenode.fetch-strategy
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
    \n
    \n
    \n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
    \n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
    \n
    \n
    \n
    \n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Wn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ie(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ie(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ie(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ie(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA,tellFailureIfAbsent:!!ie(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:ie(e?.attributesControl)?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("OriginatorAttributesConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Wn,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    \n
    tb.rulenode.originator-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Kn,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
    \n
    \n
    \n
    tb.rulenode.originator-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Zn extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of kt)this.originatorFields.push({value:e.value,name:this.translate.instant(e.name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return le(e)}prepareInputConfig(e){return{dataMapping:ie(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:ie(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[O.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Zn,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n
    \n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
    \n
    \n',dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:_n,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
    \n \n \n \n \n
    \n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Xn extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Pt,this.msgMetadataLabelTranslations=Ot,this.originatorFields=[],this.fetchToData=[];for(const e of Object.keys(kt))this.originatorFields.push({value:kt[e].value,name:this.translate.instant(kt[e].name)});for(const e of Rt.keys())this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){e.dataToFetch===Pt.FIELDS?(e.dataMapping=e.svMap,delete e.svMap):(e.dataMapping=e.kvMap,delete e.kvMap);const t={};if(e&&e.dataMapping)for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,delete e.svMap,delete e.kvMap,le(e)}prepareInputConfig(e){let t,n,r={[k.name.value]:`relatedEntity${this.translate.instant(k.name.name)}`},o={serialNumber:"sn"};return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,t===Pt.FIELDS?r=n:o=n,{relationsQuery:ie(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:t,svMap:r,kvMap:o,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[O.required]],dataToFetch:[e.dataToFetch,[]],kvMap:[e.kvMap,[O.required]],svMap:[e.svMap,[O.required]],fetchTo:[e.fetchTo,[]]})}validatorTriggers(){return["dataToFetch"]}updateValidators(e){this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.FIELDS?(this.relatedAttributesConfigForm.get("svMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("svMap").updateValueAndValidity()):(this.relatedAttributesConfigForm.get("svMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").updateValueAndValidity())}}e("RelatedAttributesConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Xn,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n
    tb.rulenode.data-to-fetch
    \n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:An,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:_n,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
    \n \n \n
    \n
    tb.rulenode.data-to-fetch
    \n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class er extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.tenantAttributesConfigForm}prepareInputConfig(e){let t,n;return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:er,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.mapping-of-tenant
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
    \n
    tb.rulenode.mapping-of-tenant
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class tr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",tr),tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:tr,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class nr{}e("RulenodeCoreConfigEnrichmentModule",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),nr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:nr,declarations:[$n,Jn,Qn,Wn,Zn,Yn,Xn,er,jn,tr],imports:[$,M,Un],exports:[$n,Jn,Qn,Wn,Zn,Yn,Xn,er,jn,tr]}),nr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,imports:[$,M,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,decorators:[{type:c,args:[{declarations:[$n,Jn,Qn,Wn,Zn,Yn,Xn,er,jn,tr],imports:[$,M,Un],exports:[$n,Jn,Qn,Wn,Zn,Yn,Xn,er,jn,tr]}]}]});class rr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Ht,this.azureIotHubCredentialsTypeTranslationsMap=jt}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[O.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[O.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),n=t.get("type").value;switch(e&&t.reset({type:n},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),n){case"sas":t.get("sasKey").setValidators([O.required]);break;case"cert.PEM":t.get("privateKey").setValidators([O.required]),t.get("privateKeyFileName").setValidators([O.required]),t.get("cert").setValidators([O.required]),t.get("certFileName").setValidators([O.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:rr,selector:"tb-external-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
    \n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:Je.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:Je.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Je.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Je.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:Je.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Ye.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:We.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"tb-external-node-azure-iot-hub-config",template:'
    \n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
    \n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class or extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Qt,this.ToByteStandartCharsetTypeTranslationMap=Jt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[O.required]],retries:[e?e.retries:null,[O.min(0)]],batchSize:[e?e.batchSize:null,[O.min(0)]],linger:[e?e.linger:null,[O.min(0)]],bufferMemory:[e?e.bufferMemory:null,[O.min(0)]],acks:[e?e.acks:null,[O.required]],keySerializer:[e?e.keySerializer:null,[O.required]],valueSerializer:[e?e.valueSerializer:null,[O.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([O.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:or,selector:"tb-external-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
    tb.rulenode.key-pattern-hint
    \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
    tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
    \n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,decorators:[{type:n,args:[{selector:"tb-external-node-kafka-config",template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
    tb.rulenode.key-pattern-hint
    \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
    tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
    \n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ar extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&me(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})}updateValidators(e){me(this.mqttConfigForm.get("clientId").value)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1}),this.mqttConfigForm.get("appendClientIdSuffix").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["clientId"]}}e("MqttConfigComponent",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ar.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ar,selector:"tb-external-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
    \n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
    {{ "tb.rulenode.client-id-suffix-hint" | translate }}
    \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:En,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,decorators:[{type:n,args:[{selector:"tb-external-node-mqtt-config",template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
    \n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
    {{ "tb.rulenode.client-id-suffix-hint" | translate }}
    \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ir extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=D,this.entityType=F}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[O.required]],targets:[e?e.targets:[],[O.required]]})}}e("NotificationConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ir,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n
    \n',dependencies:[{kind:"component",type:tt.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:nt.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
    \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[O.required]],topicName:[e?e.topicName:null,[O.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[O.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[O.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:lr,selector:"tb-external-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
    \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ye.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,decorators:[{type:n,args:[{selector:"tb-external-node-pub-sub-config",template:'
    \n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[O.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[O.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:sr,selector:"tb-external-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:We.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,decorators:[{type:n,args:[{selector:"tb-external-node-rabbit-mq-config",template:'
    \n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class mr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys($t)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[O.required]],requestMethod:[e?e.requestMethod:null,[O.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[O.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,n=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,o=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!o?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[O.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[O.required,O.min(1),O.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([O.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([O.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:mr,selector:"tb-external-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
    tb.rulenode.parse-to-plain-text-hint
    \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n
    \n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
    \n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
    \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:En,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-external-node-rest-api-call-config",template:'
    \n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
    tb.rulenode.parse-to-plain-text-hint
    \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n
    \n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
    \n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
    \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,n=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([O.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([O.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([O.required,O.min(1),O.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([O.required,O.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[O.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[O.required,O.min(1),O.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:pr,selector:"tb-external-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
    \n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
    \n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:rt.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:We.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,decorators:[{type:n,args:[{selector:"tb-external-node-send-email-config",template:'
    \n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
    \n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
    \n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class dr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[O.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[O.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([O.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",dr),dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dr,selector:"tb-external-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ot.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,decorators:[{type:n,args:[{selector:"tb-external-node-send-sms-config",template:'
    \n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ur extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(w),this.slackChanelTypesTranslateMap=V}configForm(){return this.slackConfigForm}onConfigurationSet(e){this.slackConfigForm=this.fb.group({botToken:[e?e.botToken:null],useSystemSettings:[!!e&&e.useSystemSettings],messageTemplate:[e?e.messageTemplate:null,[O.required]],conversationType:[e?e.conversationType:null,[O.required]],conversation:[e?e.conversation:null,[O.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([O.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ur.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ur,selector:"tb-external-node-slack-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
    \n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:at.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:at.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:it.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,decorators:[{type:n,args:[{selector:"tb-external-node-slack-config",template:'
    \n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
    \n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[O.required]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SnsConfigComponent",cr),cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cr,selector:"tb-external-node-sns-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,decorators:[{type:n,args:[{selector:"tb-external-node-sns-config",template:'
    \n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Bt,this.sqsQueueTypes=Object.keys(Bt),this.sqsQueueTypeTranslationsMap=Kt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[O.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[O.required]],delaySeconds:[e?e.delaySeconds:null,[O.min(0),O.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SqsConfigComponent",fr),fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fr,selector:"tb-external-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:hn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,decorators:[{type:n,args:[{selector:"tb-external-node-sqs-config",template:'
    \n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class gr{}e("RulenodeCoreConfigExternalModule",gr),gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),gr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:gr,declarations:[cr,fr,lr,or,ar,ir,sr,mr,pr,rr,dr,ur],imports:[$,M,Re,Un],exports:[cr,fr,lr,or,ar,ir,sr,mr,pr,rr,dr,ur]}),gr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,imports:[$,M,Re,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,decorators:[{type:c,args:[{declarations:[cr,fr,lr,or,ar,ir,sr,mr,pr,rr,dr,ur],imports:[$,M,Re,Un],exports:[cr,fr,lr,or,ar,ir,sr,mr,pr,rr,dr,ur]}]}]});class yr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.searchText=""}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return{alarmStatusList:ie(e?.alarmStatusList)?e.alarmStatusList:null}}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e.alarmStatusList,[O.required]]})}}e("CheckAlarmStatusComponent",yr),yr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),yr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yr,selector:"tb-filter-node-check-alarm-status-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.alarm-status
    \n
    \n tb.rulenode.alarm-required\n
    \n
    \n \n
    \n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:zn,selector:"tb-alarm-status-select"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
    \n
    \n
    tb.rulenode.alarm-status
    \n
    \n tb.rulenode.alarm-required\n
    \n
    \n \n
    \n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class xr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.checkMessageConfigForm}prepareInputConfig(e){return{messageNames:ie(e?.messageNames)?e.messageNames:[],metadataNames:ie(e?.metadataNames)?e.metadataNames:[],checkAllKeys:!!ie(e?.checkAllKeys)&&e.checkAllKeys}}prepareOutputConfig(e){return{messageNames:ie(e?.messageNames)?e.messageNames:[],metadataNames:ie(e?.metadataNames)?e.metadataNames:[],checkAllKeys:e.checkAllKeys}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e.messageNames,[]],metadataNames:[e.metadataNames,[]],checkAllKeys:[e.checkAllKeys,[]]},{validators:this.atLeastOne(O.required,["messageNames","metadataNames"])})}get touchedValidationControl(){return["messageNames","metadataNames"].some((e=>this.checkMessageConfigForm.get(e).touched))}}e("CheckMessageConfigComponent",xr),xr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),xr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xr,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.fields-to-check
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n help\n \n \n help\n \n
    \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
    \n
    \n
    tb.rulenode.fields-to-check
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n help\n \n \n help\n \n
    \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class br extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.values(v),this.entitySearchDirectionTranslationsMap=C}configForm(){return this.checkRelationConfigForm}prepareInputConfig(e){return{checkForSingleEntity:!!ie(e?.checkForSingleEntity)&&e.checkForSingleEntity,direction:ie(e?.direction)?e.direction:null,entityType:ie(e?.entityType)?e.entityType:null,entityId:ie(e?.entityId)?e.entityId:null,relationType:ie(e?.relationType)?e.relationType:null}}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[e.checkForSingleEntity,[]],direction:[e.direction,[]],entityType:[e.entityType,e&&e.checkForSingleEntity?[O.required]:[]],entityId:[e.entityId,e&&e.checkForSingleEntity?[O.required]:[]],relationType:[e.relationType,[O.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",br),br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),br.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:br,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.relation-search-parameters
    \n
    \n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
    \n
    \n \n \n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:$e.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
    \n
    tb.rulenode.relation-search-parameters
    \n
    \n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
    \n
    \n \n \n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class hr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.values(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.values(qt),this.rangeUnitTranslationMap=At,this.defaultPaddingEnable=!0}configForm(){return this.geoFilterConfigForm}prepareInputConfig(e){return{latitudeKeyName:ie(e?.latitudeKeyName)?e.latitudeKeyName:null,longitudeKeyName:ie(e?.longitudeKeyName)?e.longitudeKeyName:null,perimeterType:ie(e?.perimeterType)?e.perimeterType:null,fetchPerimeterInfoFromMessageMetadata:!!ie(e?.fetchPerimeterInfoFromMessageMetadata)&&e.fetchPerimeterInfoFromMessageMetadata,perimeterKeyName:ie(e?.perimeterKeyName)?e.perimeterKeyName:null,centerLatitude:ie(e?.centerLatitude)?e.centerLatitude:null,centerLongitude:ie(e?.centerLongitude)?e.centerLongitude:null,range:ie(e?.range)?e.range:null,rangeUnit:ie(e?.rangeUnit)?e.rangeUnit:null,polygonsDefinition:ie(e?.polygonsDefinition)?e.polygonsDefinition:null}}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e.latitudeKeyName,[O.required]],longitudeKeyName:[e.longitudeKeyName,[O.required]],perimeterType:[e.perimeterType,[O.required]],fetchPerimeterInfoFromMessageMetadata:[e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e.perimeterKeyName,[]],centerLatitude:[e.centerLatitude,[]],centerLongitude:[e.centerLongitude,[]],range:[e.range,[]],rangeUnit:[e.rangeUnit,[]],polygonsDefinition:[e.polygonsDefinition,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoFilterConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoFilterConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",hr),hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hr,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,decorators:[{type:n,args:[{selector:"tb-filter-node-gps-geofencing-config",template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class vr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}prepareInputConfig(e){return{messageTypes:ie(e?.messageTypes)?e.messageTypes:null}}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e.messageTypes,[O.required]]})}}e("MessageTypeConfigComponent",vr),vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vr,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Mn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.TENANT,F.CUSTOMER,F.USER,F.DASHBOARD,F.RULE_CHAIN,F.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}prepareInputConfig(e){return{originatorTypes:ie(e?.originatorTypes)?e.originatorTypes:null}}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e.originatorTypes,[O.required]]})}}e("OriginatorTypeConfigComponent",Cr),Cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cr,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
    \n \n help\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:st.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","additionalClasses","appearance","label","floatLabel","disabled","subscriptSizing","allowedEntityTypes","emptyInputPlaceholder","filledInputPlaceholder","ignoreAuthorityFilter"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
    \n \n help\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Fr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ie(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ie(e?.jsScript)?e.jsScript:null,tbelScript:ie(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Fr),Fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fr,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,decorators:[{type:n,args:[{selector:"tb-filter-node-script-config",template:'
    \n \n \n \n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class kr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ie(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ie(e?.jsScript)?e.jsScript:null,tbelScript:ie(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",o=this.switchConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.switchConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.switchConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",kr),kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kr,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,decorators:[{type:n,args:[{selector:"tb-filter-node-switch-config",template:'
    \n \n \n \n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Lr{}e("RuleNodeCoreConfigFilterModule",Lr),Lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Lr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Lr,declarations:[xr,br,hr,vr,Cr,Fr,kr,yr],imports:[$,M,Un],exports:[xr,br,hr,vr,Cr,Fr,kr,yr]}),Lr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,imports:[$,M,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,decorators:[{type:c,args:[{declarations:[xr,br,hr,vr,Cr,Fr,kr,yr],imports:[$,M,Un],exports:[xr,br,hr,vr,Cr,Fr,kr,yr]}]}]});class Tr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=vt,this.originatorSources=Object.keys(vt),this.originatorSourceTranslationMap=Ct,this.originatorSourceDescTranslationMap=Ft,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.USER,F.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===vt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([O.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===vt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([O.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Tr),Tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tr,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
    \n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
    \n
    \n
    \n
    \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
    \n
    \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:Ne.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:Ne.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:An,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
    \n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
    \n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
    \n
    \n
    \n
    \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
    \n
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Ir extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-transformer-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[O.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",Ir),Ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,deps:[{token:P.Store},{token:R.FormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ir,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - const Nr=mt({passive:!0});class Sr{constructor(e,t){this._platform=e,this._ngZone=t,this._monitoredElements=new Map}monitor(e){if(!this._platform.isBrowser)return ze;const t=ke(e),n=this._monitoredElements.get(t);if(n)return n.subject;const r=new _e,o="cdk-text-field-autofilled",a=e=>{"cdk-text-field-autofill-start"!==e.animationName||t.classList.contains(o)?"cdk-text-field-autofill-end"===e.animationName&&t.classList.contains(o)&&(t.classList.remove(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!1})))):(t.classList.add(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!0}))))};return this._ngZone.runOutsideAngular((()=>{t.addEventListener("animationstart",a,Nr),t.classList.add("cdk-text-field-autofill-monitored")})),this._monitoredElements.set(t,{subject:r,unlisten:()=>{t.removeEventListener("animationstart",a,Nr)}}),r}stopMonitoring(e){const t=ke(e),n=this._monitoredElements.get(t);n&&(n.unlisten(),n.subject.complete(),t.classList.remove("cdk-text-field-autofill-monitored"),t.classList.remove("cdk-text-field-autofilled"),this._monitoredElements.delete(t))}ngOnDestroy(){this._monitoredElements.forEach(((e,t)=>this.stopMonitoring(t)))}}Sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Sr,deps:[{token:pt.Platform},{token:t.NgZone}],target:t.ɵɵFactoryTarget.Injectable}),Sr.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Sr,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Sr,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:pt.Platform},{type:t.NgZone}]}});class qr{constructor(e,t){this._elementRef=e,this._autofillMonitor=t,this.cdkAutofill=new r}ngOnInit(){this._autofillMonitor.monitor(this._elementRef).subscribe((e=>this.cdkAutofill.emit(e)))}ngOnDestroy(){this._autofillMonitor.stopMonitoring(this._elementRef)}}qr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:qr,deps:[{token:t.ElementRef},{token:Sr}],target:t.ɵɵFactoryTarget.Directive}),qr.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:qr,selector:"[cdkAutofill]",outputs:{cdkAutofill:"cdkAutofill"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:qr,decorators:[{type:d,args:[{selector:"[cdkAutofill]"}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:Sr}]},propDecorators:{cdkAutofill:[{type:u}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - class Ar{get minRows(){return this._minRows}set minRows(e){this._minRows=Le(e),this._setMinHeight()}get maxRows(){return this._maxRows}set maxRows(e){this._maxRows=Le(e),this._setMaxHeight()}get enabled(){return this._enabled}set enabled(e){e=Fe(e),this._enabled!==e&&((this._enabled=e)?this.resizeToFitContent(!0):this.reset())}get placeholder(){return this._textareaElement.placeholder}set placeholder(e){this._cachedPlaceholderHeight=void 0,e?this._textareaElement.setAttribute("placeholder",e):this._textareaElement.removeAttribute("placeholder"),this._cacheTextareaPlaceholderHeight()}constructor(e,t,n,r){this._elementRef=e,this._platform=t,this._ngZone=n,this._destroyed=new _e,this._enabled=!0,this._previousMinRows=-1,this._isViewInited=!1,this._handleFocusEvent=e=>{this._hasFocus="focus"===e.type},this._document=r,this._textareaElement=this._elementRef.nativeElement}_setMinHeight(){const e=this.minRows&&this._cachedLineHeight?this.minRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.minHeight=e)}_setMaxHeight(){const e=this.maxRows&&this._cachedLineHeight?this.maxRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.maxHeight=e)}ngAfterViewInit(){this._platform.isBrowser&&(this._initialHeight=this._textareaElement.style.height,this.resizeToFitContent(),this._ngZone.runOutsideAngular((()=>{const e=this._getWindow();Ue(e,"resize").pipe(we(16),De(this._destroyed)).subscribe((()=>this.resizeToFitContent(!0))),this._textareaElement.addEventListener("focus",this._handleFocusEvent),this._textareaElement.addEventListener("blur",this._handleFocusEvent)})),this._isViewInited=!0,this.resizeToFitContent(!0))}ngOnDestroy(){this._textareaElement.removeEventListener("focus",this._handleFocusEvent),this._textareaElement.removeEventListener("blur",this._handleFocusEvent),this._destroyed.next(),this._destroyed.complete()}_cacheTextareaLineHeight(){if(this._cachedLineHeight)return;let e=this._textareaElement.cloneNode(!1);e.rows=1,e.style.position="absolute",e.style.visibility="hidden",e.style.border="none",e.style.padding="0",e.style.height="",e.style.minHeight="",e.style.maxHeight="",e.style.overflow="hidden",this._textareaElement.parentNode.appendChild(e),this._cachedLineHeight=e.clientHeight,e.remove(),this._setMinHeight(),this._setMaxHeight()}_measureScrollHeight(){const e=this._textareaElement,t=e.style.marginBottom||"",n=this._platform.FIREFOX,r=n&&this._hasFocus,o=n?"cdk-textarea-autosize-measuring-firefox":"cdk-textarea-autosize-measuring";r&&(e.style.marginBottom=`${e.clientHeight}px`),e.classList.add(o);const a=e.scrollHeight-4;return e.classList.remove(o),r&&(e.style.marginBottom=t),a}_cacheTextareaPlaceholderHeight(){if(!this._isViewInited||null!=this._cachedPlaceholderHeight)return;if(!this.placeholder)return void(this._cachedPlaceholderHeight=0);const e=this._textareaElement.value;this._textareaElement.value=this._textareaElement.placeholder,this._cachedPlaceholderHeight=this._measureScrollHeight(),this._textareaElement.value=e}ngDoCheck(){this._platform.isBrowser&&this.resizeToFitContent()}resizeToFitContent(e=!1){if(!this._enabled)return;if(this._cacheTextareaLineHeight(),this._cacheTextareaPlaceholderHeight(),!this._cachedLineHeight)return;const t=this._elementRef.nativeElement,n=t.value;if(!e&&this._minRows===this._previousMinRows&&n===this._previousValue)return;const r=this._measureScrollHeight(),o=Math.max(r,this._cachedPlaceholderHeight||0);t.style.height=`${o}px`,this._ngZone.runOutsideAngular((()=>{"undefined"!=typeof requestAnimationFrame?requestAnimationFrame((()=>this._scrollToCaretPosition(t))):setTimeout((()=>this._scrollToCaretPosition(t)))})),this._previousValue=n,this._previousMinRows=this._minRows}reset(){void 0!==this._initialHeight&&(this._textareaElement.style.height=this._initialHeight)}_noopInputHandler(){}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_scrollToCaretPosition(e){const{selectionStart:t,selectionEnd:n}=e;!this._destroyed.isStopped&&this._hasFocus&&e.setSelectionRange(t,n)}}Ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Ar,deps:[{token:t.ElementRef},{token:pt.Platform},{token:t.NgZone},{token:j,optional:!0}],target:t.ɵɵFactoryTarget.Directive}),Ar.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Ar,selector:"textarea[cdkTextareaAutosize]",inputs:{minRows:["cdkAutosizeMinRows","minRows"],maxRows:["cdkAutosizeMaxRows","maxRows"],enabled:["cdkTextareaAutosize","enabled"],placeholder:"placeholder"},host:{attributes:{rows:"1"},listeners:{input:"_noopInputHandler()"},classAttribute:"cdk-textarea-autosize"},exportAs:["cdkTextareaAutosize"],ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Ar,decorators:[{type:d,args:[{selector:"textarea[cdkTextareaAutosize]",exportAs:"cdkTextareaAutosize",host:{class:"cdk-textarea-autosize",rows:"1","(input)":"_noopInputHandler()"}}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:pt.Platform},{type:t.NgZone},{type:void 0,decorators:[{type:p},{type:m,args:[j]}]}]},propDecorators:{minRows:[{type:i,args:["cdkAutosizeMinRows"]}],maxRows:[{type:i,args:["cdkAutosizeMaxRows"]}],enabled:[{type:i,args:["cdkTextareaAutosize"]}],placeholder:[{type:i}]}}); - /** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - class Mr{}Mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Mr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,declarations:[qr,Ar],exports:[qr,Ar]}),Mr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,decorators:[{type:c,args:[{declarations:[qr,Ar],exports:[qr,Ar]}]}]});class Er extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",description:"tb.mail-body-type.plain-text-description",value:"false"},{name:"tb.mail-body-type.html",description:"tb.mail-body-type.html-text-description",value:"true"},{name:"tb.mail-body-type.use-body-type-template",description:"tb.mail-body-type.dynamic-text-description",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[O.required]],toTemplate:[e?e.toTemplate:null,[O.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[O.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null,[O.required]],bodyTemplate:[e?e.bodyTemplate:null,[O.required]]})}prepareInputConfig(e){return{fromTemplate:ie(e?.fromTemplate)?e.fromTemplate:null,toTemplate:ie(e?.toTemplate)?e.toTemplate:null,ccTemplate:ie(e?.ccTemplate)?e.ccTemplate:null,bccTemplate:ie(e?.bccTemplate)?e.bccTemplate:null,subjectTemplate:ie(e?.subjectTemplate)?e.subjectTemplate:null,mailBodyType:ie(e?.mailBodyType)?e.mailBodyType:null,isHtmlTemplate:ie(e?.isHtmlTemplate)?e.isHtmlTemplate:null,bodyTemplate:ie(e?.bodyTemplate)?e.bodyTemplate:null}}updateValidators(e){"dynamic"===this.toEmailConfigForm.get("mailBodyType").value?this.toEmailConfigForm.get("isHtmlTemplate").enable({emitEvent:!1}):this.toEmailConfigForm.get("isHtmlTemplate").disable({emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["mailBodyType"]}getBodyTypeName(){return this.mailBodyTypes.find((e=>e.value===this.toEmailConfigForm.get("mailBodyType").value)).name}}e("ToEmailConfigComponent",Er),Er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Er,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Er,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.email-sender
    \n
    \n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
    \n
    \n
    \n
    \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
    \n
    \n
    \n
    \n
    \n
    tb.rulenode.recipients
    \n \n \n
    \n
    \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
    \n
    \n
    \n
    tb.rulenode.message-subject-and-content
    \n \n \n
    \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
    \n \n {{ type.description | translate }}\n \n
    \n
    \n
    \n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:He.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Ar,selector:"textarea[cdkTextareaAutosize]",inputs:["cdkAutosizeMinRows","cdkAutosizeMaxRows","cdkTextareaAutosize","placeholder"],exportAs:["cdkTextareaAutosize"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:Ne.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:Ne.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Er,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
    \n
    \n
    tb.rulenode.email-sender
    \n
    \n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
    \n
    \n
    \n
    \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
    \n
    \n
    \n
    \n
    \n
    tb.rulenode.recipients
    \n \n \n
    \n
    \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
    \n
    \n
    \n
    tb.rulenode.message-subject-and-content
    \n \n \n
    \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
    \n \n {{ type.description | translate }}\n \n
    \n
    \n
    \n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Gr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.copyFrom=[],this.translation=tn;for(const e of this.translation.keys())this.copyFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({copyFrom:[e.copyFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}configForm(){return this.copyKeysConfigForm}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.copyFrom?en.METADATA:en.DATA:ie(e?.copyFrom)?e.copyFrom:en.DATA,{keys:ie(e?.keys)?e.keys:null,copyFrom:t}}}e("CopyKeysConfigComponent",Gr),Gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Gr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Gr,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gr,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Dr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.renameIn=[],this.translation=rn;for(const e of this.translation.keys())this.renameIn.push({value:e,name:this.translate.instant(this.translation.get(e))})}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({renameIn:[e?e.renameIn:null,[O.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[O.required]]})}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ie(e?.renameIn)?e?.renameIn:en.DATA,{renameKeysMapping:ie(e?.renameKeysMapping)?e.renameKeysMapping:null,renameIn:t}}}e("RenameKeysConfigComponent",Dr),Dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Dr,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.rename-keys-in
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Sn,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dr,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
    \n
    tb.rulenode.rename-keys-in
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class wr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.jsonPathConfigForm}onConfigurationSet(e){this.jsonPathConfigForm=this.fb.group({jsonPath:[e?e.jsonPath:null,[O.required]]})}}e("NodeJsonPathConfigComponent",wr),wr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wr,selector:"tb-transformation-node-json-path-config",usesInheritance:!0,ngImport:t,template:"
    \n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
    \n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,decorators:[{type:n,args:[{selector:"tb-transformation-node-json-path-config",template:"
    \n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
    \n"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Vr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.deleteFrom=[],this.translation=nn;for(const e of this.translation.keys())this.deleteFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({deleteFrom:[e.deleteFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ie(e?.deleteFrom)?e?.deleteFrom:en.DATA,{keys:ie(e?.keys)?e.keys:null,deleteFrom:t}}configForm(){return this.deleteKeysConfigForm}}e("DeleteKeysConfigComponent",Vr),Vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Vr,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:Ze.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:On,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.deduplicationStrategie=Gt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=Dt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[ie(e?.interval)?e.interval:null,[O.required,O.min(1)]],strategy:[ie(e?.strategy)?e.strategy:null,[O.required]],outMsgType:[ie(e?.outMsgType)?e.outMsgType:null,[O.required]],maxPendingMsgs:[ie(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e3)]],maxRetries:[ie(e?.maxRetries)?e.maxRetries:null,[O.required,O.min(0),O.max(100)]]})}prepareInputConfig(e){return e||(e={}),e.outMsgType||(e.outMsgType="POST_TELEMETRY_REQUEST"),super.prepareInputConfig(e)}updateValidators(e){this.deduplicationConfigForm.get("strategy").value===this.deduplicationStrategie.ALL?this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}):this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("outMsgType").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["strategy"]}}e("DeduplicationConfigComponent",Pr),Pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Pr,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    tb.rulenode.strategy
    \n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
    \n \n \n
    \n
    \n
    \n \n \n tb.rulenode.advanced-settings\n \n
    \n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Je.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:Je.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:Je.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Xe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:et.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:Rn,selector:"tb-output-message-type-autocomplete",inputs:["subscriptSizing","disabled","required"]},{kind:"component",type:Nn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:'
    \n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    tb.rulenode.strategy
    \n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
    \n \n \n
    \n
    \n
    \n \n \n tb.rulenode.advanced-settings\n \n
    \n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Rr{}e("RulenodeCoreConfigTransformModule",Rr),Rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Rr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Rr,declarations:[Tr,Ir,Er,Gr,Dr,wr,Vr,Pr],imports:[$,M,Un],exports:[Tr,Ir,Er,Gr,Dr,wr,Vr,Pr]}),Rr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,imports:[$,M,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,decorators:[{type:c,args:[{declarations:[Tr,Ir,Er,Gr,Dr,wr,Vr,Pr],imports:[$,M,Un],exports:[Tr,Ir,Er,Gr,Dr,wr,Vr,Pr]}]}]});class Or extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=F}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[O.required]]})}}e("RuleChainInputComponent",Or),Or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Or,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class _r extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",_r),_r.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),_r.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:_r,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    \n',dependencies:[{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Br{}e("RuleNodeCoreConfigFlowModule",Br),Br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Br.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Br,declarations:[Or,_r],imports:[$,M,Un],exports:[Or,_r]}),Br.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,imports:[$,M,Un]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,decorators:[{type:c,args:[{declarations:[Or,_r],imports:[$,M,Un],exports:[Or,_r]}]}]});class Kr{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{id:"Id","additional-info":"Additional Info","advanced-settings":"Advanced settings","create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","copy-message-type":"Copy message type","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","message-type-value":"Message type value","message-type-value-required":"Message type value is required","message-type-value-max-length":"Message type value should be less than 256","output-message-type":"Output message type","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","interval-start":"Interval start","interval-end":"Interval end","time-unit":"Time unit","fetch-mode":"Fetch mode","order-by-timestamp":"Order by timestamp",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.","limit-required":"Limit is required.","limit-range":"Limit should be in a range from 2 to 1000.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Allowing range from 1 to 2147483647.","start-interval-value-required":"Interval start is required.","end-interval-value-required":"Interval end is required.",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","add-message-type":"Add message type","select-message-types-required":"At least one message type should be selected.","select-message-types":"Select message types","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one.","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","notify-device":"Force notification to the device","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","update-attributes-only-on-value-change":"Save attributes only if the value changes","update-attributes-only-on-value-change-hint":"Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.","update-attributes-only-on-value-change-hint-enabled":"Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-on-update-hint":"If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.","notify-device-on-delete-hint":"If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","timeseries-keys-required":"At least one timeseries key should be selected.","add-timeseries-key":"Add timeseries key","add-message-field":"Add message field","relation-search-parameters":"Relation search parameters","add-metadata-field":"Add metadata field","data-keys":"Message field names","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.",interval:"Interval","interval-required":"Interval is required","interval-hint":"Deduplication interval in seconds.","interval-min-error":"Min allowed value is 1","max-pending-msgs":"Max pending messages","max-pending-msgs-hint":"Maximum number of messages that are stored in memory for each unique deduplication id.","max-pending-msgs-required":"Max pending messages is required","max-pending-msgs-max-error":"Max allowed value is 1000","max-pending-msgs-min-error":"Min allowed value is 1","max-retries":"Max retries","max-retries-required":"Max retries is required","max-retries-hint":"Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries","max-retries-max-error":"Max allowed value is 100","max-retries-min-error":"Min allowed value is 0",strategy:"Strategy","strategy-required":"Strategy is required","strategy-all-hint":"Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.","strategy-first-hint":"Return first message that arrived during deduplication period.","strategy-last-hint":"Return last message that arrived during deduplication period.",first:"First",last:"Last",all:"All","output-msg-type-hint":"The message type of the deduplication result.","queue-name-hint":"The queue name where the deduplication result will be published.",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",message:"Message",metadata:"Metadata","current-key-name":"Current key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Metadata field names","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","max-relation-level-error":"Value should be greater than 0 or unspecified.","relation-type":"Relation type","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","add-telemetry-key":"Add telemetry key","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.","fetch-into":"Fetch into","attr-mapping":"Attributes mapping:","source-attribute":"Source attribute key","source-attribute-required":"Source attribute key is required.","source-telemetry":"Source telemetry key","source-telemetry-required":"Source telemetry key is required.","target-key":"Target key","target-key-required":"Target key is required.","attr-mapping-required":"At least one mapping entry should be specified.","fields-mapping":"Fields mapping","relations-query-config-direction-suffix":"originator","profile-name":"Profile name","fetch-circle-parameter-info-from-metadata-hint":'Metadata field \'{{perimeterKeyName}}\' should be defined in next format: {"latitude":48.196, "longitude":24.6532, "radius":100.0, "radiusUnit":"METER"}',"fetch-poligon-parameter-info-from-metadata-hint":"Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]","short-templatization-tooltip":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","fields-mapping-required":"At least one field mapping should be specified.","at-least-one-field-required":"At least one input field must have a value(s) provided.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","new-originator":"New originator","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related entity","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity by name pattern","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","select-entity-types":"Select entity types","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From","from-template-required":"From is required","message-to-metadata":"Message to metadata","metadata-to-message":"Metadata to message","from-message":"From message","from-metadata":"From metadata","to-template":"To","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc","bcc-template":"Bcc","subject-template":"Subject","subject-template-required":"Subject Template is required","body-template":"Body","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","body-type-template":"Body type template","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","parse-to-plain-text":"Parse to plain text","parse-to-plain-text-hint":'If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = "Hello,\\t\\"world\\"" will be parsed to Hello, "world"',"read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file","private-key":"Client private key file",cert:"Client certificate file","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-dynamic-interval":"Use dynamic interval","metadata-dynamic-interval-hint":"Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all specified fields are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-to-specific-entity-tooltip":"If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval":"Interval start","end-interval":"Interval end","start-interval-required":"Interval start is required.","end-interval-required":"Interval end is required.","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'Press "Enter" to complete field input.',"select-details":"Select details","entity-details-id":"Id","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","email-sender":"Email sender","fields-to-check":"Fields to check","add-detail":"Add detail","check-all-keys-tooltip":"If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.","fields-to-check-hint":'Press "Enter" to complete field name input. Multiple field names supported.',"entity-details-list-empty":"At least one detail should be selected.","alarm-status":"Alarm status","alarm-required":"At least one alarm status should be selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"Enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-field-name":"Latitude field name","longitude-field-name":"Longitude field name","latitude-field-name-required":"Latitude field name is required.","longitude-field-name-required":"Longitude field name is required.","fetch-perimeter-info-from-metadata":"Fetch perimeter information from metadata","fetch-perimeter-info-from-metadata-tooltip":"If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.","perimeter-key-name":"Perimeter key name","perimeter-key-name-hint":"Metadata field name that includes perimeter information.","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units","range-units-required":"Range units is required.",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch timestamp for the latest telemetry values","get-latest-value-with-ts-hint":'If selected, the latest telemetry values will also include timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.","number-of-digits-after-floating-point":"Number of digits after floating point","number-of-digits-after-floating-point-range":"Number of digits after floating point should be in a range from 0 to 15.","failure-if-delta-negative":"Tell Failure if delta is negative","failure-if-delta-negative-tooltip":"Rule node forces failure of message processing if delta value is negative.","use-caching":"Use caching","use-caching-tooltip":'Rule node will cache the value of "{{inputValueKey}}" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the "{{inputValueKey}}" value elsewhere.',"add-time-difference-between-readings":'Add the time difference between "{{inputValueKey}}" readings',"add-time-difference-between-readings-tooltip":'If enabled, the rule node will add the "{{periodValueKey}}" to the outbound message.',"period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":"Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.","alarm-severity-pattern-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":"All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time-series","message-body-type":"Message","message-metadata-type":"Metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-source-field-input":"Source","argument-source-field-input-required":"Argument source is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","add-entity-type":"Add entity type","add-device-profile":"Add device profile","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Use 0 to convert result to integer","add-to-message-field-input":"Add to message","add-to-metadata-field-input":"Add to metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius","retained-message":"Retained","attributes-mapping":"Attributes mapping","latest-telemetry-mapping":"Latest telemetry mapping","add-mapped-attribute-to":"Add mapped attributes to","add-mapped-latest-telemetry-to":"Add mapped latest telemetry to","add-mapped-fields-to":"Add mapped fields to","add-selected-details-to":"Add selected details to","clear-selected-types":"Clear selected types","clear-selected-details":"Clear selected details","clear-selected-fields":"Clear selected fields","clear-selected-keys":"Clear selected keys","geofence-configuration":"Geofence configuration","coordinate-field-names":"Coordinate field names","coordinate-field-hint":"Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.","fetch-credentials-to":"Fetch credentials to","add-originator-attributes-to":"Add originator attributes to","originator-attributes":"Originator attributes","fetch-latest-telemetry-with-timestamp":"Fetch latest telemetry with timestamp","fetch-latest-telemetry-with-timestamp-tooltip":'If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell failure if any of the attributes are missing","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time","chip-help":"Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.",detail:"detail","field-name":"field name","device-profile":"device profile","entity-type":"entity type","message-type":"message type","timeseries-key":"timeseries key",type:"Type","first-name":"First name","last-name":"Last name",label:"Label","originator-fields-mapping":"Originator fields mapping","add-mapped-originator-fields-to":"Add mapped originator fields to",fields:"Fields","skip-empty-fields":"Skip empty fields","skip-empty-fields-tooltip":"Fields with empty values will not be added to the output message/output metadata.","fetch-interval":"Fetch interval","fetch-strategy":"Fetch strategy","fetch-timeseries-from-to":"Fetch timeseries from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.","fetch-timeseries-from-to-invalid":'Fetch timeseries invalid ("Interval start" should be less than "Interval end").',"use-metadata-dynamic-interval-tooltip":"If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.","all-mode-hint":'If selected fetch mode "All" rule node will retrieve telemetry from the fetch interval with configurable query parameters.',"first-mode-hint":'If selected fetch mode "First" rule node will retrieve the closest telemetry to the fetch interval\'s start.',"last-mode-hint":'If selected fetch mode "Last" rule node will retrieve the closest telemetry to the fetch interval\'s end.',ascending:"Ascending",descending:"Descending",min:"Min",max:"Max",average:"Average",sum:"Sum",count:"Count",none:"None","last-level-relation-tooltip":"If selected, the rule node will search related entities only on the level set in the max relation level.","last-level-device-relation-tooltip":"If selected, the rule node will search related devices only on the level set in the max relation level.","data-to-fetch":"Data to fetch","mapping-of-customers":"Mapping of customer's","map-fields-required":"All mapping fields are required.",attributes:"Attributes","related-device-attributes":"Related device attributes","add-selected-attributes-to":"Add selected attributes to","device-profiles":"Device profiles","mapping-of-tenant":"Mapping of tenant's","add-attribute-key":"Add attribute key","message-template":"Message template","message-template-required":"Message template is required","use-system-slack-settings":"Use system slack settings","slack-api-token":"Slack API token","slack-api-token-required":"Slack API token is required","keys-mapping":"keys mapping","add-key":"Add key",recipients:"Recipients","message-subject-and-content":"Message subject and content","template-rules-hint":"Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","originator-customer-desc":"Use customer of incoming message originator as new originator.","originator-tenant-desc":"Use current tenant as new originator.","originator-related-entity-desc":"Use related entity as new originator. Lookup based on configured relation type and direction.","originator-alarm-originator-desc":"Use alarm originator as new originator. Only if incoming message originator is alarm entity.","originator-entity-by-name-pattern-desc":"Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.","email-from-template-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","recipients-block-main-hint":"Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata."},"key-val":{key:"Key",value:"Value","see-examples":"See examples.","remove-entry":"Remove entry","remove-mapping-entry":"Remove mapping entry","add-mapping-entry":"Add mapping","add-entry":"Add entry","copy-key-values-from":"Copy key-values from","delete-key-values":"Delete key-values","delete-key-values-from":"Delete key-values from","at-least-one-key-error":"At least one key should be selected.","unique-key-value-pair-error":"'{{keyText}}' must be different from the '{{valText}}'!"},"mail-body-type":{"plain-text":"Plain text",html:"HTML",dynamic:"Dynamic","use-body-type-template":"Use body type template","plain-text-description":"Simple, unformatted text with no special styling or formating.","html-text-description":"Allows you to use HTML tags for formatting, links and images in your mai body.","dynamic-text-description":"Allows to use Plain Text or HTML body type dynamically based on templatization feature.","after-template-evaluation-hint":"After template evaluation value should be true for HTML, and false for Plain text."}}},!0)}(e)}}e("RuleNodeCoreConfigModule",Kr),Kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,deps:[{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),Kr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Kr,declarations:[dt],imports:[$,M],exports:[Hn,Lr,nr,gr,Rr,Br,dt]}),Kr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,imports:[$,M,Hn,Lr,nr,gr,Rr,Br]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,decorators:[{type:c,args:[{declarations:[dt],imports:[$,M],exports:[Hn,Lr,nr,gr,Rr,Br,dt]}]}],ctorParameters:function(){return[{type:Z.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map +System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/common","@angular/material/checkbox","@angular/material/input","@angular/material/form-field","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/select","@angular/material/core","@angular/material/slide-toggle","@shared/components/hint-tooltip-icon.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/material/icon","@angular/material/tooltip","@shared/components/script-lang.component","@angular/cdk/keycodes","@angular/material/chips","@shared/pipe/safe.pipe","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@shared/components/toggle-header.component","@shared/components/toggle-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/flex-layout/extended","@angular/material/list","@angular/cdk/drag-drop","rxjs/operators","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@home/components/public-api","tslib","rxjs","@shared/components/help-popup.component","@shared/components/entity/entity-subtype-list.component","@shared/components/relation/relation-type-autocomplete.component","@home/components/relation/relation-filters.component","@angular/material/expansion","@shared/components/file-input.component","@shared/components/button/toggle-password.component","@shared/components/string-items-list.component","@shared/components/entity/entity-list.component","@shared/components/notification/template-autocomplete.component","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/radio","@shared/components/slack-conversation-autocomplete.component","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component","@angular/cdk/platform"],(function(e){"use strict";var t,n,r,o,a,i,l,s,m,p,d,u,c,f,g,y,x,b,h,v,C,F,k,L,T,I,N,S,q,A,M,E,G,D,w,V,P,R,O,_,B,K,z,U,H,j,$,Q,J,Y,W,Z,X,ee,te,ne,re,oe,ae,ie,le,se,me,pe,de,ue,ce,fe,ge,ye,xe,be,he,ve,Ce,Fe,ke,Le,Te,Ie,Ne,Se,qe,Ae,Me,Ee,Ge,De,we,Ve,Pe,Re,Oe,_e,Be,Ke,ze,Ue,He,je,$e,Qe,Je,Ye,We,Ze,Xe,et,tt,nt,rt,ot,at,it,lt,st,mt,pt;return{setters:[function(e){t=e,n=e.Component,r=e.EventEmitter,o=e.ViewChild,a=e.forwardRef,i=e.Input,l=e.InjectionToken,s=e.Injectable,m=e.Inject,p=e.Optional,d=e.Directive,u=e.Output,c=e.NgModule},function(e){f=e.RuleNodeConfigurationComponent,g=e.AttributeScope,y=e.telemetryTypeTranslations,x=e.ScriptLanguage,b=e.AlarmSeverity,h=e.alarmSeverityTranslations,v=e.EntitySearchDirection,C=e.entitySearchDirectionTranslations,F=e.EntityType,k=e.entityFields,L=e.PageComponent,T=e.coerceBoolean,I=e.MessageType,N=e.messageTypeNames,S=e,q=e.AlarmStatus,A=e.alarmStatusTranslations,M=e.SharedModule,E=e.AggregationType,G=e.aggregationTranslations,D=e.NotificationType,w=e.SlackChanelType,V=e.SlackChanelTypesTranslateMap},function(e){P=e},function(e){R=e,O=e.Validators,_=e.NgControl,B=e.NG_VALUE_ACCESSOR,K=e.NG_VALIDATORS,z=e.FormArray,U=e.FormGroup},function(e){H=e,j=e.DOCUMENT,$=e.CommonModule},function(e){Q=e},function(e){J=e},function(e){Y=e},function(e){W=e},function(e){Z=e},function(e){X=e},function(e){ee=e},function(e){te=e},function(e){ne=e},function(e){re=e.getCurrentAuthState,oe=e,ae=e.isEqual,ie=e.isDefinedAndNotNull,le=e.deepTrim,se=e.isObject,me=e.isNotEmptyStr},function(e){pe=e},function(e){de=e},function(e){ue=e},function(e){ce=e},function(e){fe=e},function(e){ge=e.ENTER,ye=e.COMMA,xe=e.SEMICOLON},function(e){be=e},function(e){he=e},function(e){ve=e},function(e){Ce=e},function(e){Fe=e},function(e){ke=e},function(e){Le=e.coerceBooleanProperty,Te=e.coerceElement,Ie=e.coerceNumberProperty},function(e){Ne=e},function(e){Se=e},function(e){qe=e},function(e){Ae=e},function(e){Me=e.tap,Ee=e.map,Ge=e.startWith,De=e.mergeMap,we=e.share,Ve=e.takeUntil,Pe=e.auditTime},function(e){Re=e},function(e){Oe=e},function(e){_e=e.HomeComponentsModule},function(e){Be=e.__decorate},function(e){Ke=e.Subject,ze=e.takeUntil,Ue=e.of,He=e.EMPTY,je=e.fromEvent},function(e){$e=e},function(e){Qe=e},function(e){Je=e},function(e){Ye=e},function(e){We=e},function(e){Ze=e},function(e){Xe=e},function(e){et=e},function(e){tt=e},function(e){nt=e},function(e){rt=e},function(e){ot=e},function(e){at=e},function(e){it=e},function(e){lt=e},function(e){st=e},function(e){mt=e.normalizePassiveListenerOptions,pt=e}],execute:function(){class dt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dt,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
    ",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dt,decorators:[{type:n,args:[{selector:"tb-node-empty-config",template:"
    "}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ut extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ut,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ut,decorators:[{type:n,args:[{selector:"tb-action-node-assign-to-customer-config",template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ct extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],notifyDevice:[!e||e.notifyDevice,[]],sendAttributesUpdatedNotification:[!!e&&e.sendAttributesUpdatedNotification,[]],updateAttributesOnlyOnValueChange:[!!e&&e.updateAttributesOnlyOnValueChange,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),e===g.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1}),this.attributesConfigForm.get("updateAttributesOnlyOnValueChange").patchValue(!1,{emitEvent:!1})}))}}e("AttributesConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ct,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ct,decorators:[{type:n,args:[{selector:"tb-action-node-attributes-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.update-attributes-only-on-value-change\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ft extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],alarmType:[e?e.alarmType:null,[O.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.clearAlarmConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(t===x.JS?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(t===x.TBEL?[O.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.clearAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",o=this.clearAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.clearAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ft,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ft,decorators:[{type:n,args:[{selector:"tb-action-node-clear-alarm-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gt extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.alarmSeverities=Object.keys(b),this.alarmSeverityTranslationMap=h,this.separatorKeysCodes=[ge,ye,xe],this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-details-function"}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[]],alarmDetailsBuildTbel:[e?e.alarmDetailsBuildTbel:null,[]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(e){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,n=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;t?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([O.required]),this.createAlarmConfigForm.get("severity").setValidators([O.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==x.TBEL||this.tbelEnabled||(r=x.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const o=!1===t||!0===n;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(o&&r===x.JS?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").setValidators(o&&r===x.TBEL?[O.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.createAlarmConfigForm.get("scriptLang").value,n=t===x.JS?"alarmDetailsBuildJs":"alarmDetailsBuildTbel",r=t===x.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",o=this.createAlarmConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.createAlarmConfigForm.get(n).setValue(e),this.changeScript.emit())}))}removeKey(e,t){const n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!e||t){this.createAlarmConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}}e("CreateAlarmConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gt,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
    \n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
    \n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gt,decorators:[{type:n,args:[{selector:"tb-action-node-create-alarm-config",template:'
    \n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
    \n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
    \n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class yt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[O.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==F.DEVICE&&t!==F.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([O.required,O.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yt,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
    tb.rulenode.create-entity-if-not-exists-hint
    \n
    \n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
    tb.rulenode.remove-current-relations-hint
    \n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
    tb.rulenode.change-originator-to-related-entity-hint
    \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yt,decorators:[{type:n,args:[{selector:"tb-action-node-create-relation-config",template:'
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
    tb.rulenode.create-entity-if-not-exists-hint
    \n
    \n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
    tb.rulenode.remove-current-relations-hint
    \n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
    tb.rulenode.change-originator-to-related-entity-hint
    \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xt extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.entityType=F}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[O.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[O.required,O.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,n=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([O.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&n?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
    tb.rulenode.delete-relation-hint
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xt,decorators:[{type:n,args:[{selector:"tb-action-node-delete-relation-config",template:'
    \n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
    tb.rulenode.delete-relation-hint
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bt extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,O.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,O.required]})}}e("DeviceProfileConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bt,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
    \n',dependencies:[{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bt,decorators:[{type:n,args:[{selector:"tb-device-profile-config",template:'
    \n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ht extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-generator-function"}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[O.required,O.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[O.required,O.min(1)]],originator:[e?e.originator:null,[]],scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.generatorConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.generatorConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.generatorConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.generatorConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS),e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(e){const t=this.generatorConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",o=this.generatorConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.generatorConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}var vt;e("GeneratorConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ht,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
    \n \n \n \n
    \n\n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ce.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ht,decorators:[{type:n,args:[{selector:"tb-action-node-generator-config",template:'
    \n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
    \n \n \n \n
    \n\n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(vt||(vt={}));const Ct=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer"],[vt.TENANT,"tb.rulenode.originator-tenant"],[vt.RELATED,"tb.rulenode.originator-related"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[vt.ENTITY,"tb.rulenode.originator-entity"]]),Ft=new Map([[vt.CUSTOMER,"tb.rulenode.originator-customer-desc"],[vt.TENANT,"tb.rulenode.originator-tenant-desc"],[vt.RELATED,"tb.rulenode.originator-related-entity-desc"],[vt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator-desc"],[vt.ENTITY,"tb.rulenode.originator-entity-by-name-pattern-desc"]]),kt=[k.createdTime,k.name,{value:"type",name:"tb.rulenode.profile-name",keyName:"originatorProfileName"},k.firstName,k.lastName,k.email,k.title,k.country,k.state,k.city,k.address,k.address2,k.zip,k.phone,k.label,{value:"id",name:"tb.rulenode.id",keyName:"id"},{value:"additionalInfo",name:"tb.rulenode.additional-info",keyName:"additionalInfo"}],Lt=new Map([["type","profileName"],["createdTime","createdTime"],["name","name"],["firstName","firstName"],["lastName","lastName"],["email","email"],["title","title"],["country","country"],["state","state"],["city","city"],["address","address"],["address2","address2"],["zip","zip"],["phone","phone"],["label","label"],["id","id"],["additionalInfo","additionalInfo"]]);var Tt;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Tt||(Tt={}));const It=new Map([[Tt.CIRCLE,"tb.rulenode.perimeter-circle"],[Tt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Nt;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(Nt||(Nt={}));const St=new Map([[Nt.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Nt.SECONDS,"tb.rulenode.time-unit-seconds"],[Nt.MINUTES,"tb.rulenode.time-unit-minutes"],[Nt.HOURS,"tb.rulenode.time-unit-hours"],[Nt.DAYS,"tb.rulenode.time-unit-days"]]);var qt;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(qt||(qt={}));const At=new Map([[qt.METER,"tb.rulenode.range-unit-meter"],[qt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[qt.FOOT,"tb.rulenode.range-unit-foot"],[qt.MILE,"tb.rulenode.range-unit-mile"],[qt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Mt;!function(e){e.ID="ID",e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Mt||(Mt={}));const Et=new Map([[Mt.ID,"tb.rulenode.entity-details-id"],[Mt.TITLE,"tb.rulenode.entity-details-title"],[Mt.COUNTRY,"tb.rulenode.entity-details-country"],[Mt.STATE,"tb.rulenode.entity-details-state"],[Mt.CITY,"tb.rulenode.entity-details-city"],[Mt.ZIP,"tb.rulenode.entity-details-zip"],[Mt.ADDRESS,"tb.rulenode.entity-details-address"],[Mt.ADDRESS2,"tb.rulenode.entity-details-address2"],[Mt.PHONE,"tb.rulenode.entity-details-phone"],[Mt.EMAIL,"tb.rulenode.entity-details-email"],[Mt.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Gt;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Gt||(Gt={}));const Dt=new Map([[Gt.FIRST,"tb.rulenode.first"],[Gt.LAST,"tb.rulenode.last"],[Gt.ALL,"tb.rulenode.all"]]),wt=new Map([[Gt.FIRST,"tb.rulenode.first-mode-hint"],[Gt.LAST,"tb.rulenode.last-mode-hint"],[Gt.ALL,"tb.rulenode.all-mode-hint"]]);var Vt,Pt;!function(e){e.ASC="ASC",e.DESC="DESC"}(Vt||(Vt={})),function(e){e.ATTRIBUTES="ATTRIBUTES",e.LATEST_TELEMETRY="LATEST_TELEMETRY",e.FIELDS="FIELDS"}(Pt||(Pt={}));const Rt=new Map([[Pt.ATTRIBUTES,"tb.rulenode.attributes"],[Pt.LATEST_TELEMETRY,"tb.rulenode.latest-telemetry"],[Pt.FIELDS,"tb.rulenode.fields"]]),Ot=new Map([[Pt.ATTRIBUTES,"tb.rulenode.add-mapped-attribute-to"],[Pt.LATEST_TELEMETRY,"tb.rulenode.add-mapped-latest-telemetry-to"],[Pt.FIELDS,"tb.rulenode.add-mapped-fields-to"]]),_t=new Map([[Vt.ASC,"tb.rulenode.ascending"],[Vt.DESC,"tb.rulenode.descending"]]);var Bt;!function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Bt||(Bt={}));const Kt=new Map([[Bt.STANDARD,"tb.rulenode.sqs-queue-standard"],[Bt.FIFO,"tb.rulenode.sqs-queue-fifo"]]),zt=["anonymous","basic","cert.PEM"],Ut=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Ht=["sas","cert.PEM"],jt=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var $t;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}($t||($t={}));const Qt=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Jt=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var Yt;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(Yt||(Yt={}));const Wt=new Map([[Yt.CUSTOM,{value:Yt.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[Yt.ADD,{value:Yt.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[Yt.SUB,{value:Yt.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[Yt.MULT,{value:Yt.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[Yt.DIV,{value:Yt.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[Yt.SIN,{value:Yt.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.SINH,{value:Yt.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[Yt.COS,{value:Yt.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[Yt.COSH,{value:Yt.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[Yt.TAN,{value:Yt.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[Yt.TANH,{value:Yt.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ACOS,{value:Yt.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[Yt.ASIN,{value:Yt.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN,{value:Yt.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[Yt.ATAN2,{value:Yt.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[Yt.EXP,{value:Yt.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[Yt.EXPM1,{value:Yt.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[Yt.SQRT,{value:Yt.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[Yt.CBRT,{value:Yt.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[Yt.GET_EXP,{value:Yt.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[Yt.HYPOT,{value:Yt.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[Yt.LOG,{value:Yt.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG10,{value:Yt.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[Yt.LOG1P,{value:Yt.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[Yt.CEIL,{value:Yt.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR,{value:Yt.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[Yt.FLOOR_DIV,{value:Yt.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[Yt.FLOOR_MOD,{value:Yt.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[Yt.ABS,{value:Yt.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[Yt.MIN,{value:Yt.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[Yt.MAX,{value:Yt.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[Yt.POW,{value:Yt.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[Yt.SIGNUM,{value:Yt.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[Yt.RAD,{value:Yt.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[Yt.DEG,{value:Yt.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var Zt,Xt,en;!function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT"}(Zt||(Zt={})),function(e){e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA",e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES"}(Xt||(Xt={})),function(e){e.DATA="DATA",e.METADATA="METADATA"}(en||(en={}));const tn=new Map([[en.DATA,"tb.rulenode.message-to-metadata"],[en.METADATA,"tb.rulenode.metadata-to-message"]]),nn=(new Map([[en.DATA,"tb.rulenode.from-message"],[en.METADATA,"tb.rulenode.from-metadata"]]),new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.metadata"]])),rn=new Map([[en.DATA,"tb.rulenode.message"],[en.METADATA,"tb.rulenode.message-metadata"]]),on=new Map([[Zt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Fetch argument value from incoming message"}],[Zt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Fetch argument value from incoming message metadata"}],[Zt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Fetch attribute value from database"}],[Zt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Fetch latest time-series value from database"}],[Zt.CONSTANT,{name:"tb.rulenode.constant-type",description:"Define constant value"}]]),an=new Map([[Xt.MESSAGE_BODY,{name:"tb.rulenode.message-body-type",description:"Add result to the outgoing message"}],[Xt.MESSAGE_METADATA,{name:"tb.rulenode.message-metadata-type",description:"Add result to the outgoing message metadata"}],[Xt.ATTRIBUTE,{name:"tb.rulenode.attribute-type",description:"Store result as an entity attribute in the database"}],[Xt.TIME_SERIES,{name:"tb.rulenode.time-series-type",description:"Store result as an entity time-series in the database"}]]),ln=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var sn,mn;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(sn||(sn={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(mn||(mn={}));const pn=new Map([[sn.SHARED_SCOPE,"tb.rulenode.shared-scope"],[sn.SERVER_SCOPE,"tb.rulenode.server-scope"],[sn.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);var dn;!function(e){e.ON_FIRST_MESSAGE="ON_FIRST_MESSAGE",e.ON_EACH_MESSAGE="ON_EACH_MESSAGE"}(dn||(dn={}));const un=new Map([[dn.ON_EACH_MESSAGE,{value:!0,name:"tb.rulenode.presence-monitoring-strategy-on-each-message"}],[dn.ON_FIRST_MESSAGE,{value:!1,name:"tb.rulenode.presence-monitoring-strategy-on-first-message"}]]);class cn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.keys(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.keys(qt),this.rangeUnitTranslationMap=At,this.presenceMonitoringStrategies=un,this.presenceMonitoringStrategyKeys=Array.from(this.presenceMonitoringStrategies.keys()),this.timeUnits=Object.keys(Nt),this.timeUnitsTranslationMap=St,this.defaultPaddingEnable=!0}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({reportPresenceStatusOnEachMessage:[!e||e.reportPresenceStatusOnEachMessage,[O.required]],latitudeKeyName:[e?e.latitudeKeyName:null,[O.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[O.required]],perimeterType:[e?e.perimeterType:null,[O.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[O.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[O.required,O.min(1),O.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[O.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoActionConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoActionConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",cn),cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cn,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n
    \n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n
    \n
    \n
    {{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
    \n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
    \n
    {{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
    \n
    \n
    \n
    \n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cn,decorators:[{type:n,args:[{selector:"tb-action-node-gps-geofencing-config",template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n
    \n \n tb.rulenode.polygon-definition\n \n \n help\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n
    \n
    \n
    {{ \'tb.rulenode.presence-monitoring-strategy\' | translate }}
    \n \n \n {{ presenceMonitoringStrategies.get(strategy).name | translate }}\n \n \n
    \n
    {{ geoActionConfigForm.get(\'reportPresenceStatusOnEachMessage\').value === false ?\n (\'tb.rulenode.presence-monitoring-strategy-on-first-message-hint\' | translate) :\n (\'tb.rulenode.presence-monitoring-strategy-on-each-message-hint\' | translate) }}\n
    \n
    \n
    \n
    \n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fn extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-to-string-function"}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.logConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.logConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.logConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.logConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.logConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",o=this.logConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.logConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.logConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",fn),fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fn,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fn,decorators:[{type:n,args:[{selector:"tb-action-node-log-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class gn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[O.required,O.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[O.required]]})}}e("MsgCountConfigComponent",gn),gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gn,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-count-config",template:'
    \n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class yn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([O.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([O.required,O.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",yn),yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yn,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
    tb.rulenode.use-metadata-period-in-seconds-patterns-hint
    \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yn,decorators:[{type:n,args:[{selector:"tb-action-node-msg-delay-config",template:'
    \n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
    tb.rulenode.use-metadata-period-in-seconds-patterns-hint
    \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToCloudConfigComponent",xn),xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:xn,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-cloud-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class bn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]]})}}e("PushToEdgeConfigComponent",bn),bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:bn,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:bn,decorators:[{type:n,args:[{selector:"tb-action-node-push-to-edge-config",template:'
    \n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class hn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",hn),hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hn,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.request-id-metadata-attribute\n \n \n
    \n',dependencies:[{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-reply-config",template:'
    \n \n tb.rulenode.request-id-metadata-attribute\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class vn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[O.required,O.min(0)]]})}}e("RpcRequestConfigComponent",vn),vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vn,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vn,decorators:[{type:n,args:[{selector:"tb-action-node-rpc-request-config",template:'
    \n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const n of Object.keys(e))Object.prototype.hasOwnProperty.call(e,n)&&t.push(this.fb.group({key:[n,[O.required]],value:[e[n],[O.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[O.required]],value:["",[O.required]]}))}validate(e){const t=this.kvListFormGroup.get("keyVals").value;if(!t.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const e of t)if(e.key===e.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigOldComponent",Cn),Cn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cn,selector:"tb-kv-map-config-old",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n {{ keyText | translate }}\n {{ valText | translate }}\n \n
    \n
    \n
    \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Ne.TbErrorComponent,selector:"tb-error",inputs:["noMargin","error"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cn,decorators:[{type:n,args:[{selector:"tb-kv-map-config-old",providers:[{provide:B,useExisting:a((()=>Cn)),multi:!0},{provide:K,useExisting:a((()=>Cn)),multi:!0}],template:'
    \n
    \n {{ keyText | translate }}\n {{ valText | translate }}\n \n
    \n
    \n
    \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#757575;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .header .tb-required:after{color:#757575;font-size:12px;font-weight:700}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:0;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host .tb-kv-map-config tb-error{display:block;margin-top:-12px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],required:[{type:i}]}});class Fn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[O.required,O.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[O.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Fn),Fn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fn,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fn,decorators:[{type:n,args:[{selector:"tb-action-node-custom-table-config",template:'
    \n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class kn extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[O.required,O.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",kn),kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kn,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
    tb.rulenode.use-server-ts-hint
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kn,decorators:[{type:n,args:[{selector:"tb-action-node-timeseries-config",template:'
    \n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
    tb.rulenode.use-server-ts-hint
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Ln extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[O.required,O.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[O.required,O.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Ln),Ln.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ln.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Ln,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ln,decorators:[{type:n,args:[{selector:"tb-action-node-un-assign-to-customer-config",template:'
    \n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Tn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopeMap=g,this.attributeScopes=Object.keys(g),this.telemetryTypeTranslationsMap=y,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(e){this.deleteAttributesConfigForm=this.fb.group({scope:[e?e.scope:null,[O.required]],keys:[e?e.keys:null,[O.required]],sendAttributesDeletedNotification:[!!e&&e.sendAttributesDeletedNotification,[]],notifyDevice:[!!e&&e.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((e=>{e!==g.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}removeKey(e){const t=this.deleteAttributesConfigForm.get("keys").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}addKey(e){const t=e.input;let n=e.value;if((n||"").trim()){n=n.trim();let e=this.deleteAttributesConfigForm.get("keys").value;e&&-1!==e.indexOf(n)||(e||(e=[]),e.push(n),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("DeleteAttributesConfigComponent",Tn),Tn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tn,selector:"tb-action-node-delete-attributes-config",viewQueries:[{propertyName:"attributeChipList",first:!0,predicate:["attributeChipList"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
    tb.rulenode.send-attributes-deleted-notification-hint
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    tb.rulenode.notify-device-on-delete-hint
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tn,decorators:[{type:n,args:[{selector:"tb-action-node-delete-attributes-config",template:'
    \n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n {{ \'tb.rulenode.attributes-keys\' | translate }}\n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
    tb.rulenode.send-attributes-deleted-notification-hint
    \n
    \n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
    tb.rulenode.notify-device-on-delete-hint
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]},propDecorators:{attributeChipList:[{type:o,args:["attributeChipList"]}]}});class In extends L{get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup(!0))}constructor(e,t){super(e),this.store=e,this.fb=t,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=Wt,this.ArgumentType=Zt,this.attributeScopeMap=pn,this.argumentTypeMap=on,this.arguments=Object.values(Zt),this.attributeScope=Object.values(sn),this.propagateChange=null,this.valueChangeSubscription=[]}ngOnInit(){this.argumentsFormGroup=this.fb.group({arguments:this.fb.array([])}),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray,n=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,n),this.updateArgumentNames()}get argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):(this.argumentsFormGroup.enable({emitEvent:!1}),this.argumentsFormArray.controls.forEach((e=>this.updateArgumentControlValidators(e))))}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){const t=[];e&&e.forEach(((e,n)=>{t.push(this.createArgumentControl(e,n))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t),{emitEvent:!1}),this.setupArgumentsFormGroup()}removeArgument(e){this.argumentsFormArray.removeAt(e),this.updateArgumentNames()}addArgument(e=!0){const t=this.argumentsFormArray,n=this.createArgumentControl(null,t.length);t.push(n,{emitEvent:e})}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(e=!1){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===Yt.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([O.minLength(this.minArgs),O.maxLength(this.maxArgs)]);this.argumentsFormArray.length>this.maxArgs;)this.removeArgument(this.maxArgs-1);for(;this.argumentsFormArray.length{this.updateArgumentControlValidators(n),n.get("attributeScope").updateValueAndValidity({emitEvent:!1}),n.get("defaultValue").updateValueAndValidity({emitEvent:!1})}))),n}updateArgumentControlValidators(e){const t=e.get("type").value;t===Zt.ATTRIBUTE?e.get("attributeScope").enable({emitEvent:!1}):e.get("attributeScope").disable({emitEvent:!1}),t&&t!==Zt.CONSTANT?e.get("defaultValue").enable({emitEvent:!1}):e.get("defaultValue").disable({emitEvent:!1})}updateArgumentNames(){this.argumentsFormArray.controls.forEach(((e,t)=>{e.get("name").setValue(ln[t])}))}updateModel(){const e=this.argumentsFormArray.value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",In),In.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),In.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:In,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n\n
    \n \n \n
    \n \n
    \n {{argumentControl.get(\'name\').value}}.\n
    \n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
    \n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n tb.rulenode.no-arguments-prompt\n
    \n \n
    \n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:qe.MatList,selector:"mat-list",exportAs:["matList"]},{kind:"component",type:qe.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["activated"],exportAs:["matListItem"]},{kind:"directive",type:Ae.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","cdkDropListData","cdkDropListOrientation","id","cdkDropListLockAxis","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListAutoScrollDisabled","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{kind:"directive",type:Ae.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragData","cdkDragLockAxis","cdkDragRootElement","cdkDragBoundary","cdkDragStartDelay","cdkDragFreeDragPosition","cdkDragDisabled","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragPreviewContainer"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{kind:"directive",type:Ae.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:In,decorators:[{type:n,args:[{selector:"tb-arguments-map-config",providers:[{provide:B,useExisting:a((()=>In)),multi:!0},{provide:K,useExisting:a((()=>In)),multi:!0}],template:'
    \n\n
    \n \n \n
    \n \n
    \n {{argumentControl.get(\'name\').value}}.\n
    \n \n tb.rulenode.argument-source-field-input\n \n \n {{ argumentTypeMap.get(argumentControl.get(\'type\').value)?.name | translate }}\n \n \n {{ argumentTypeMap.get(argument).name | translate }}\n \n {{ argumentTypeMap.get(argument).description }}\n \n \n \n \n tb.rulenode.argument-source-field-input-required\n \n \n
    \n \n tb.rulenode.argument-key-field-input\n \n \n help\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n tb.rulenode.no-arguments-prompt\n
    \n \n
    \n',styles:[":host .mat-mdc-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:16px}:host .arguments-list{padding:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],function:[{type:i}]}});class Nn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...Wt.values()],this.propagateChange=null}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(Me((e=>{let t;t="string"==typeof e&&Yt[e]?Yt[e]:null,this.updateView(t)})),Ee((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=Wt.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}e("MathFunctionAutocompleteComponent",Nn),Nn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Nn,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nn,decorators:[{type:n,args:[{selector:"tb-math-function-autocomplete",providers:[{provide:B,useExisting:a((()=>Nn)),multi:!0}],template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n'}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.UntypedFormBuilder}]},propDecorators:{required:[{type:i}],disabled:[{type:i}],operationInput:[{type:o,args:["operationInput",{static:!0}]}]}});class Sn extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=Yt,this.ArgumentTypeResult=Xt,this.argumentTypeResultMap=an,this.attributeScopeMap=pn,this.argumentsResult=Object.values(Xt),this.attributeScopeResult=Object.values(mn)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[O.required]],arguments:[e?e.arguments:null,[O.required]],customFunction:[e?e.customFunction:"",[O.required]],result:this.fb.group({type:[e?e.result.type:null,[O.required]],attributeScope:[e?e.result.attributeScope:null,[O.required]],key:[e?e.result.key:"",[O.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,n=this.mathFunctionConfigForm.get("result.type").value;t===Yt.CUSTOM?(this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}),null===this.mathFunctionConfigForm.get("customFunction").value&&this.mathFunctionConfigForm.get("customFunction").patchValue("(x - 32) / 1.8",{emitEvent:!1})):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),n===Xt.ATTRIBUTE?this.mathFunctionConfigForm.get("result.attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result.attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result.attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}}e("MathFunctionConfigComponent",Sn),Sn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Sn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sn,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n tb.rulenode.argument-tile\n \n \n
    \n
    \n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
    \n
    \n tb.rulenode.result-title\n
    \n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
    \n
    \n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
    \n
    \n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
    \n
    \n
    \n
    \n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:In,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{kind:"component",type:Nn,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sn,decorators:[{type:n,args:[{selector:"tb-action-node-math-function-config",template:'
    \n \n \n
    \n tb.rulenode.argument-tile\n \n \n
    \n
    \n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n tb.rulenode.custom-expression-field-input-hint\n \n
    \n
    \n tb.rulenode.result-title\n
    \n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(mathFunctionConfigForm.get(\'result.type\').value)?.name | translate }}\n \n \n {{ argumentTypeResultMap.get(argument).name | translate }}\n \n {{ argumentTypeResultMap.get(argument).description }}\n \n \n \n \n tb.rulenode.type-field-input-required\n \n \n
    \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n help\n \n tb.rulenode.key-field-input-required\n \n \n
    \n
    \n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
    \n
    \n \n {{\'tb.rulenode.add-to-message-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
    \n
    \n
    \n
    \n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group .mat-mdc-form-field .mat-mdc-form-field-infix{width:100%}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class qn{constructor(){this.textAlign="left"}}e("ExampleHintComponent",qn),qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,deps:[],target:t.ɵɵFactoryTarget.Component}),qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:qn,selector:"tb-example-hint",inputs:{hintText:"hintText",popupHelpLink:"popupHelpLink",textAlign:"textAlign"},ngImport:t,template:'
    \n
    \n
    \n
    \n
    \n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:qn,decorators:[{type:n,args:[{selector:"tb-example-hint",template:'
    \n
    \n
    \n
    \n
    \n',styles:[":host .space-between{display:flex;justify-content:space-between;gap:20px}:host .space-between .see-example{display:flex;flex-shrink:0}:host .hint-text{width:100%}\n"]}]}],propDecorators:{hintText:[{type:i}],popupHelpLink:[{type:i}],textAlign:[{type:i}]}});class An{constructor(e,t){this.injector=e,this.fb=t,this.propagateChange=()=>{},this.destroy$=new Ke,this.disabled=!1,this.uniqueKeyValuePairValidator=!1,this.required=!1,this.duplicateValuesValidator=e=>e.controls.key.value===e.controls.value.value&&e.controls.key.value&&e.controls.value.value?{uniqueKeyValuePair:!0}:null,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.kvListFormGroup&&this.kvListFormGroup.get("keyVals")&&"VALID"===this.kvListFormGroup.get("keyVals")?.status)return null;const t={};if(this.kvListFormGroup&&this.kvListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ae(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.kvListFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))})),this.kvListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1})}}removeKeyVal(e){this.keyValsFormArray().removeAt(e)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]},{validators:this.uniqueKeyValuePairValidator?[this.duplicateValuesValidator]:[]}))}validate(){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",An),An.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,deps:[{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),An.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:An,selector:"tb-kv-map-config",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",labelText:"labelText",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>An)),multi:!0},{provide:K,useExisting:a((()=>An)),multi:!0}],ngImport:t,template:'
    \n
    \n
    {{ labelText }}
    \n
    \n {{ requiredText }}\n
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
    \n
    \n
    \n
    \n
    \n
    {{ keyText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([T()],An.prototype,"disabled",void 0),Be([T()],An.prototype,"uniqueKeyValuePairValidator",void 0),Be([T()],An.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:An,decorators:[{type:n,args:[{selector:"tb-kv-map-config",providers:[{provide:B,useExisting:a((()=>An)),multi:!0},{provide:K,useExisting:a((()=>An)),multi:!0}],template:'
    \n
    \n
    {{ labelText }}
    \n
    \n {{ requiredText }}\n
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ \'tb.key-val.unique-key-value-pair-error\' | translate:\n {\n valText: valText,\n keyText: keyText\n } }}\n
    \n
    \n
    \n
    \n
    \n
    {{ keyText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],uniqueKeyValuePairValidator:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],keyText:[{type:i}],keyRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class Mn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.entityType=F,this.propagateChange=null}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],relationType:[null],deviceTypes:[null,[O.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",Mn),Mn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Mn,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n \n \n help\n \n
    \n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Qe.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","floatLabel","label","disabled","entityType","emptyInputPlaceholder","filledInputPlaceholder","appearance","subscriptSizing","additionalClasses"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Mn,decorators:[{type:n,args:[{selector:"tb-device-relations-query-config",providers:[{provide:B,useExisting:a((()=>Mn)),multi:!0}],template:'
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n \n \n help\n \n
    \n',styles:[":host .last-level-slide-toggle{margin:8px 0 24px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class En extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.values(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[O.min(1)]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",En),En.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),En.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:En,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.relations-query
    \n
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n
    \n
    \n
    relation.relation-filters
    \n \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:En,decorators:[{type:n,args:[{selector:"tb-relations-query-config",providers:[{provide:B,useExisting:a((()=>En)),multi:!0}],template:'
    \n
    tb.rulenode.relations-query
    \n
    \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n {{ \'tb.rulenode.max-relation-level-error\' | translate }}\n \n \n
    \n
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n
    \n
    \n
    relation.relation-filters
    \n \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Gn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.truncate=n,this.fb=r,this.placeholder="tb.rulenode.add-message-type",this.separatorKeysCodes=[ge,ye,xe],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(I))this.messageTypesList.push({name:N.get(I[e]),value:e})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(Ge(""),Ee((e=>e||"")),De((e=>this.fetchMessageTypes(e))),we())}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return e&&e.length>0}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ue(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ue(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t;const n=e.trim(),r=this.messageTypesList.find((e=>e.name===n));t=r?{name:r.name,value:r.value}:{name:n,value:n},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",Gn),Gn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,deps:[{token:P.Store},{token:Z.TranslateService},{token:S.TruncatePipe},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Gn,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
    \n
    \n tb.rulenode.no-message-types-found\n
    \n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
    \n
    \n
    \n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:Re.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple","hideSingleSelectionIndicator"],exportAs:["matAutocomplete"]},{kind:"directive",type:Re.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{kind:"directive",type:Re.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{kind:"component",type:be.MatChipGrid,selector:"mat-chip-grid",inputs:["tabIndex","disabled","placeholder","required","value","errorStateMatcher"],outputs:["change","valueChange"]},{kind:"directive",type:be.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputFor","matChipInputAddOnBlur","matChipInputSeparatorKeyCodes","placeholder","id","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{kind:"directive",type:be.MatChipRemove,selector:"[matChipRemove]"},{kind:"component",type:be.MatChipRow,selector:"mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]",inputs:["color","disabled","disableRipple","tabIndex","editable"],outputs:["edited"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Oe.HighlightPipe,name:"highlight"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Gn,decorators:[{type:n,args:[{selector:"tb-message-types-config",providers:[{provide:B,useExisting:a((()=>Gn)),multi:!0}],template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
    \n
    \n tb.rulenode.no-message-types-found\n
    \n \n \n {{ \'tb.rulenode.no-message-type-matching\' | translate :\n {messageType: truncate.transform(searchText, true, 6, '...')}\n }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
    \n
    \n
    \n help\n \n {{ \'tb.rulenode.select-message-types-required\' | translate }}\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:S.TruncatePipe},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],label:[{type:i}],placeholder:[{type:i}],disabled:[{type:i}],chipList:[{type:o,args:["chipList",{static:!1}]}],matAutocomplete:[{type:o,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:o,args:["messageTypeInput",{static:!1}]}]}});class Dn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRequired=!0,this.allCredentialsTypes=zt,this.credentialsTypeTranslationsMap=Ut,this.propagateChange=e=>{}}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[O.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const n=e[t];if(!n.firstChange&&n.currentValue!==n.previousValue&&n.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){ie(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators())}setDisabledState(e){e?this.credentialsConfigFormGroup.disable({emitEvent:!1}):(this.credentialsConfigFormGroup.enable({emitEvent:!1}),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([O.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRequired?[O.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(O.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return n=>{t||(t=[Object.keys(n.controls)]);return n?.controls&&t.some((t=>t.every((t=>!e(n.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",Dn),Dn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Dn,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRequired:"passwordFieldRequired"},providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0},{provide:K,useExisting:a((()=>Dn)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
    \n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
    {{ \'tb.rulenode.credentials-pem-hint\' | translate }}
    \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
    \n
    \n
    \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:We.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dn,decorators:[{type:n,args:[{selector:"tb-credentials-config",providers:[{provide:B,useExisting:a((()=>Dn)),multi:!0},{provide:K,useExisting:a((()=>Dn)),multi:!0}],template:'
    \n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
    {{ \'tb.rulenode.credentials-pem-hint\' | translate }}
    \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
    \n
    \n
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{required:[{type:i}],disableCertPemCredentials:[{type:i}],passwordFieldRequired:[{type:i}]}});const wn=new l("WindowToken","undefined"!=typeof window&&window.document?{providedIn:"root",factory:()=>window}:{providedIn:"root",factory:()=>{}});class Vn{constructor(e,t,n){this.ngZone=e,this.document=t,this.window=n,this.copySubject=new Ke,this.copyResponse$=this.copySubject.asObservable(),this.config={}}configure(e){this.config=e}copy(e){if(!this.isSupported||!e)return this.pushCopyResponse({isSuccess:!1,content:e});const t=this.copyFromContent(e);return t?this.pushCopyResponse({content:e,isSuccess:t}):this.pushCopyResponse({isSuccess:!1,content:e})}get isSupported(){return!!this.document.queryCommandSupported&&!!this.document.queryCommandSupported("copy")&&!!this.window}isTargetValid(e){if(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement){if(e.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');return!0}throw new Error("Target should be input or textarea")}copyFromInputElement(e,t=!0){try{this.selectTarget(e);const n=this.copyText();return this.clearSelection(t?e:void 0,this.window),n&&this.isCopySuccessInIE11()}catch(e){return!1}}isCopySuccessInIE11(){const e=this.window.clipboardData;return!(e&&e.getData&&!e.getData("Text"))}copyFromContent(e,t=this.document.body){if(this.tempTextArea&&!t.contains(this.tempTextArea)&&this.destroy(this.tempTextArea.parentElement||void 0),!this.tempTextArea){this.tempTextArea=this.createTempTextArea(this.document,this.window);try{t.appendChild(this.tempTextArea)}catch(e){throw new Error("Container should be a Dom element")}}this.tempTextArea.value=e;const n=this.copyFromInputElement(this.tempTextArea,!1);return this.config.cleanUpAfterCopy&&this.destroy(this.tempTextArea.parentElement||void 0),n}destroy(e=this.document.body){this.tempTextArea&&(e.removeChild(this.tempTextArea),this.tempTextArea=void 0)}selectTarget(e){return e.select(),e.setSelectionRange(0,e.value.length),e.value.length}copyText(){return this.document.execCommand("copy")}clearSelection(e,t){e&&e.focus(),t.getSelection()?.removeAllRanges()}createTempTextArea(e,t){const n="rtl"===e.documentElement.getAttribute("dir");let r;r=e.createElement("textarea"),r.style.fontSize="12pt",r.style.border="0",r.style.padding="0",r.style.margin="0",r.style.position="absolute",r.style[n?"right":"left"]="-9999px";const o=t.pageYOffset||e.documentElement.scrollTop;return r.style.top=o+"px",r.setAttribute("readonly",""),r}pushCopyResponse(e){this.copySubject.observers.length>0&&this.ngZone.run((()=>{this.copySubject.next(e)}))}pushCopyReponse(e){this.pushCopyResponse(e)}}Vn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Vn,deps:[{token:t.NgZone},{token:j},{token:wn,optional:!0}],target:t.ɵɵFactoryTarget.Injectable}),Vn.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Vn,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Vn,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:void 0,decorators:[{type:m,args:[j]}]},{type:void 0,decorators:[{type:p},{type:m,args:[wn]}]}]}});class Pn{constructor(e,t,n,o){this.ngZone=e,this.host=t,this.renderer=n,this.clipboardSrv=o,this.cbOnSuccess=new r,this.cbOnError=new r,this.onClick=e=>{this.clipboardSrv.isSupported?this.targetElm&&this.clipboardSrv.isTargetValid(this.targetElm)?this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm),this.targetElm.value,e):this.cbContent&&this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent,this.container),this.cbContent,e):this.handleResult(!1,void 0,e)}}ngOnInit(){this.ngZone.runOutsideAngular((()=>{this.clickListener=this.renderer.listen(this.host.nativeElement,"click",this.onClick)}))}ngOnDestroy(){this.clickListener&&this.clickListener(),this.clipboardSrv.destroy(this.container)}handleResult(e,t,n){let r={isSuccess:e,content:t,successMessage:this.cbSuccessMsg,event:n};e?this.cbOnSuccess.observed&&this.ngZone.run((()=>{this.cbOnSuccess.emit(r)})):this.cbOnError.observed&&this.ngZone.run((()=>{this.cbOnError.emit(r)})),this.clipboardSrv.pushCopyResponse(r)}}Pn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,deps:[{token:t.NgZone},{token:t.ElementRef},{token:t.Renderer2},{token:Vn}],target:t.ɵɵFactoryTarget.Directive}),Pn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:Pn,selector:"[ngxClipboard]",inputs:{targetElm:["ngxClipboard","targetElm"],container:"container",cbContent:"cbContent",cbSuccessMsg:"cbSuccessMsg"},outputs:{cbOnSuccess:"cbOnSuccess",cbOnError:"cbOnError"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Pn,decorators:[{type:d,args:[{selector:"[ngxClipboard]"}]}],ctorParameters:function(){return[{type:t.NgZone},{type:t.ElementRef},{type:t.Renderer2},{type:Vn}]},propDecorators:{targetElm:[{type:i,args:["ngxClipboard"]}],container:[{type:i}],cbContent:[{type:i}],cbSuccessMsg:[{type:i}],cbOnSuccess:[{type:u}],cbOnError:[{type:u}]}});class Rn{constructor(e,t,n){this._clipboardService=e,this._viewContainerRef=t,this._templateRef=n}ngOnInit(){this._clipboardService.isSupported&&this._viewContainerRef.createEmbeddedView(this._templateRef)}}Rn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,deps:[{token:Vn},{token:t.ViewContainerRef},{token:t.TemplateRef}],target:t.ɵɵFactoryTarget.Directive}),Rn.ɵdir=t.ɵɵngDeclareDirective({minVersion:"12.0.0",version:"13.0.1",type:Rn,selector:"[ngxClipboardIfSupported]",ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:Rn,decorators:[{type:d,args:[{selector:"[ngxClipboardIfSupported]"}]}],ctorParameters:function(){return[{type:Vn},{type:t.ViewContainerRef},{type:t.TemplateRef}]}});class On{}On.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,deps:[],target:t.ɵɵFactoryTarget.NgModule}),On.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,declarations:[Pn,Rn],imports:[$],exports:[Pn,Rn]}),On.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,imports:[[$]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"13.0.1",ngImport:t,type:On,decorators:[{type:c,args:[{imports:[$],declarations:[Pn,Rn],exports:[Pn,Rn]}]}]});class _n{set required(e){this.requiredValue!==e&&(this.requiredValue=e,this.updateValidators())}get required(){return this.requiredValue}constructor(e){this.fb=e,this.subscriptSizing="fixed",this.messageTypes=[{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},{name:"Custom",value:""}],this.propagateChange=()=>{},this.destroy$=new Ke,this.messageTypeFormGroup=this.fb.group({messageTypeAlias:[null,[O.required]],messageType:[{value:null,disabled:!0},[O.maxLength(255)]]}),this.messageTypeFormGroup.get("messageTypeAlias").valueChanges.pipe(ze(this.destroy$)).subscribe((e=>this.updateMessageTypeValue(e))),this.messageTypeFormGroup.valueChanges.pipe(ze(this.destroy$)).subscribe((()=>this.updateView()))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}registerOnTouched(e){}registerOnChange(e){this.propagateChange=e}writeValue(e){this.modelValue=e;let t=this.messageTypes.find((t=>t.value===e));t||(t=this.messageTypes.find((e=>""===e.value))),this.messageTypeFormGroup.get("messageTypeAlias").patchValue(t,{emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e,{emitEvent:!1})}validate(){return this.messageTypeFormGroup.valid?null:{messageTypeInvalid:!0}}setDisabledState(e){this.disabled=e,e?this.messageTypeFormGroup.disable({emitEvent:!1}):(this.messageTypeFormGroup.enable({emitEvent:!1}),"Custom"!==this.messageTypeFormGroup.get("messageTypeAlias").value?.name&&this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}))}updateView(){const e=this.messageTypeFormGroup.getRawValue().messageType;this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}updateValidators(){this.messageTypeFormGroup.get("messageType").setValidators(this.required?[O.required,O.maxLength(255)]:[O.maxLength(255)]),this.messageTypeFormGroup.get("messageType").updateValueAndValidity({emitEvent:!1})}updateMessageTypeValue(e){"Custom"!==e?.name?this.messageTypeFormGroup.get("messageType").disable({emitEvent:!1}):this.messageTypeFormGroup.get("messageType").enable({emitEvent:!1}),this.messageTypeFormGroup.get("messageType").patchValue(e.value??null)}}e("OutputMessageTypeAutocompleteComponent",_n),_n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_n,deps:[{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_n.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:_n,selector:"tb-output-message-type-autocomplete",inputs:{subscriptSizing:"subscriptSizing",disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>_n)),multi:!0},{provide:K,useExisting:a((()=>_n)),multi:!0}],ngImport:t,template:'
    \n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
    \n\n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Pn,selector:"[ngxClipboard]",inputs:["ngxClipboard","container","cbContent","cbSuccessMsg"],outputs:["cbOnSuccess","cbOnError"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([T()],_n.prototype,"disabled",void 0),Be([T()],_n.prototype,"required",null),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_n,decorators:[{type:n,args:[{selector:"tb-output-message-type-autocomplete",providers:[{provide:B,useExisting:a((()=>_n)),multi:!0},{provide:K,useExisting:a((()=>_n)),multi:!0}],template:'
    \n \n {{\'tb.rulenode.output-message-type\' | translate}}\n \n \n {{msgType.name}}\n \n \n \n \n {{\'tb.rulenode.message-type-value\' | translate}}\n \n \n \n {{ \'tb.rulenode.message-type-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.message-type-value-max-length\' | translate }}\n \n \n
    \n\n'}]}],ctorParameters:function(){return[{type:R.FormBuilder}]},propDecorators:{subscriptSizing:[{type:i}],disabled:[{type:i}],required:[{type:i}]}});class Bn{constructor(e,t){this.fb=e,this.translate=t,this.translation=nn,this.propagateChange=()=>{},this.destroy$=new Ke,this.selectOptions=[]}ngOnInit(){this.initOptions(),this.chipControlGroup=this.fb.group({chipControl:[null,[]]}),this.chipControlGroup.get("chipControl").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{e&&this.propagateChange(e)}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}initOptions(){for(const e of this.translation.keys())this.selectOptions.push({value:e,name:this.translate.instant(this.translation.get(e))})}writeValue(e){this.chipControlGroup.get("chipControl").patchValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){e?this.chipControlGroup.disable({emitEvent:!1}):this.chipControlGroup.enable({emitEvent:!1})}}e("MsgMetadataChipComponent",Bn),Bn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,deps:[{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Bn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Bn,selector:"tb-msg-metadata-chip",inputs:{labelText:"labelText",translation:"translation"},providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0}],ngImport:t,template:'
    \n
    {{ labelText }}
    \n \n {{ option.name }}\n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Bn,decorators:[{type:n,args:[{selector:"tb-msg-metadata-chip",providers:[{provide:B,useExisting:a((()=>Bn)),multi:!0}],template:'
    \n
    {{ labelText }}
    \n \n {{ option.name }}\n \n
    \n'}]}],ctorParameters:function(){return[{type:R.FormBuilder},{type:Z.TranslateService}]},propDecorators:{labelText:[{type:i}],translation:[{type:i}]}});class Kn extends L{constructor(e,t,n,r){super(e),this.store=e,this.translate=t,this.injector=n,this.fb=r,this.destroy$=new Ke,this.sourceFieldSubcritption=[],this.propagateChange=null,this.disabled=!1,this.required=!1,this.oneMapRequiredValidator=e=>e.get("keyVals").value.length,this.propagateNestedErrors=e=>{if(this.svListFormGroup&&this.svListFormGroup.get("keyVals")&&"VALID"===this.svListFormGroup.get("keyVals")?.status)return null;const t={};if(this.svListFormGroup&&this.svListFormGroup.setErrors(null),e instanceof z||e instanceof U){if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;for(const n of Object.keys(e.controls)){const r=this.propagateNestedErrors(e.controls[n]);if(r&&Object.keys(r).length)for(const e of Object.keys(r))t[e]=!0}return t}if(e.errors)for(const n of Object.keys(e.errors))t[n]=!0;return ae(t,{})?null:t}}ngOnInit(){this.ngControl=this.injector.get(_),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.svListFormGroup=this.fb.group({keyVals:this.fb.array([])},{validators:[this.propagateNestedErrors,this.oneMapRequiredValidator]}),this.svListFormGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((()=>{this.updateModel()}))}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}keyValsFormArray(){return this.svListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.svListFormGroup.disable({emitEvent:!1}):this.svListFormGroup.enable({emitEvent:!1})}writeValue(e){const t=Object.keys(e).map((t=>({key:t,value:e[t]})));if(this.keyValsFormArray().length===t.length)this.keyValsFormArray().patchValue(t,{emitEvent:!1});else{const e=[];t.forEach((t=>{e.push(this.fb.group({key:[t.key,[O.required]],value:[t.value,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]}))})),this.svListFormGroup.setControl("keyVals",this.fb.array(e,this.propagateNestedErrors),{emitEvent:!1});for(const e of this.keyValsFormArray().controls)this.keyChangeSubscribe(e)}}filterSelectOptions(e){const t=[];for(const e of this.svListFormGroup.get("keyVals").value){const n=this.selectOptions.find((t=>t.value===e.key));n&&t.push(n)}const n=[];for(const r of this.selectOptions)ie(t.find((e=>e.value===r.value)))&&r.value!==e?.get("key").value||n.push(r);return n}removeKeyVal(e){this.keyValsFormArray().removeAt(e),this.sourceFieldSubcritption[e].unsubscribe(),this.sourceFieldSubcritption.splice(e,1)}addKeyVal(){this.keyValsFormArray().push(this.fb.group({key:["",[O.required]],value:["",[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]]})),this.keyChangeSubscribe(this.keyValsFormArray().at(this.keyValsFormArray().length-1))}keyChangeSubscribe(e){this.sourceFieldSubcritption.push(e.get("key").valueChanges.pipe(Ve(this.destroy$)).subscribe((t=>{const n=Lt.get(t);e.get("value").patchValue(this.targetKeyPrefix+n[0].toUpperCase()+n.slice(1))})))}validate(e){return!this.svListFormGroup.get("keyVals").value.length&&this.required?{svMapRequired:!0}:this.svListFormGroup.valid?null:{svFieldsRequired:!0}}updateModel(){const e=this.svListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.svListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("SvMapConfigComponent",Kn),Kn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,deps:[{token:P.Store},{token:Z.TranslateService},{token:t.Injector},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kn,selector:"tb-sv-map-config",inputs:{selectOptions:"selectOptions",disabled:"disabled",labelText:"labelText",requiredText:"requiredText",targetKeyPrefix:"targetKeyPrefix",selectText:"selectText",selectRequiredText:"selectRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",popupHelpLink:"popupHelpLink",required:"required"},providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0},{provide:K,useExisting:a((()=>Kn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    {{ labelText }}
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ requiredText }}\n
    \n
    \n
    \n
    \n
    \n
    {{ selectText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n {{option.name}}\n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:Se.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormControlDirective,selector:"[formControl]",inputs:["formControl","disabled","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:H.AsyncPipe,name:"async"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),Be([T()],Kn.prototype,"disabled",void 0),Be([T()],Kn.prototype,"required",void 0),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kn,decorators:[{type:n,args:[{selector:"tb-sv-map-config",providers:[{provide:B,useExisting:a((()=>Kn)),multi:!0},{provide:K,useExisting:a((()=>Kn)),multi:!0}],template:'
    \n
    \n
    {{ labelText }}
    \n
    \n tb.rulenode.map-fields-required\n
    \n
    \n {{ requiredText }}\n
    \n
    \n
    \n
    \n
    \n
    {{ selectText }}
    \n
    {{ valText }}
    \n
    \n
    \n
    \n
    \n \n \n \n {{option.name}}\n \n \n \n \n \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n',styles:[":host .field-space{flex:1 1 50%}:host .actions-header{width:40px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:t.Injector},{type:R.FormBuilder}]},propDecorators:{selectOptions:[{type:i}],disabled:[{type:i}],labelText:[{type:i}],requiredText:[{type:i}],targetKeyPrefix:[{type:i}],selectText:[{type:i}],selectRequiredText:[{type:i}],valText:[{type:i}],valRequiredText:[{type:i}],hintText:[{type:i}],popupHelpLink:[{type:i}],required:[{type:i}]}});class zn extends L{get required(){return this.requiredValue}set required(e){this.requiredValue=Le(e)}constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(v),this.directionTypeTranslations=C,this.propagateChange=null}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[O.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigOldComponent",zn),zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:zn,selector:"tb-relations-query-config-old",inputs:{disabled:"disabled",required:"required"},providers:[{provide:B,useExisting:a((()=>zn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
    \n
    relation.relation-filters
    \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Ye.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zn,decorators:[{type:n,args:[{selector:"tb-relations-query-config-old",providers:[{provide:B,useExisting:a((()=>zn)),multi:!0}],template:'
    \n \n {{ \'alias.last-level-relation\' | translate }}\n \n
    \n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
    \n
    relation.relation-filters
    \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]},propDecorators:{disabled:[{type:i}],required:[{type:i}]}});class Un{constructor(e,t){this.translate=e,this.fb=t,this.propagateChange=e=>{},this.destroy$=new Ke,this.separatorKeysCodes=[ge,ye,xe],this.onTouched=()=>{}}ngOnInit(){this.attributeControlGroup=this.fb.group({clientAttributeNames:[[],[]],sharedAttributeNames:[[],[]],serverAttributeNames:[[],[]],latestTsKeyNames:[[],[]],getLatestValueWithTs:[!1,[]]},{validators:this.atLeastOne(O.required,["clientAttributeNames","sharedAttributeNames","serverAttributeNames","latestTsKeyNames"])}),this.attributeControlGroup.valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(this.preparePropagateValue(e))}))}preparePropagateValue(e){const t={};for(const n in e)t[n]="getLatestValueWithTs"===n||ie(e[n])?e[n]:[];return t}validate(){return this.attributeControlGroup.valid?null:{atLeastOneRequired:!0}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}writeValue(e){this.attributeControlGroup.setValue(e,{emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){this.onTouched=e}setDisabledState(e){e?this.attributeControlGroup.disable({emitEvent:!1}):this.attributeControlGroup.enable({emitEvent:!1})}ngOnDestroy(){this.destroy$.next(null),this.destroy$.complete()}}e("SelectAttributesComponent",Un),Un.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,deps:[{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Un.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Un,selector:"tb-select-attributes",inputs:{popupHelpLink:"popupHelpLink"},providers:[{provide:B,useExisting:a((()=>Un)),multi:!0},{provide:K,useExisting:Un,multi:!0}],ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
    \n
    \n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgTemplateOutlet,selector:"[ngTemplateOutlet]",inputs:["ngTemplateOutletContext","ngTemplateOutlet","ngTemplateOutletInjector"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Un,decorators:[{type:n,args:[{selector:"tb-select-attributes",providers:[{provide:B,useExisting:a((()=>Un)),multi:!0},{provide:K,useExisting:Un,multi:!0}],template:'
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-latest-telemetry-with-timestamp\' | translate }}\n \n
    \n
    \n\n\n help\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:Z.TranslateService},{type:R.FormBuilder}]},propDecorators:{popupHelpLink:[{type:i}]}});class Hn extends L{constructor(e,t){super(e),this.store=e,this.fb=t,this.propagateChange=null,this.destroy$=new Ke,this.alarmStatus=q,this.alarmStatusTranslations=A}ngOnInit(){this.alarmStatusGroup=this.fb.group({alarmStatus:[null,[]]}),this.alarmStatusGroup.get("alarmStatus").valueChanges.pipe(Ve(this.destroy$)).subscribe((e=>{this.propagateChange(e)}))}setDisabledState(e){e?this.alarmStatusGroup.disable({emitEvent:!1}):this.alarmStatusGroup.enable({emitEvent:!1})}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnDestroy(){this.destroy$.next(),this.destroy$.complete()}writeValue(e){this.alarmStatusGroup.get("alarmStatus").patchValue(e,{emitEvent:!1})}}e("AlarmStatusSelectComponent",Hn),Hn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Hn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Hn,selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
    \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
    \n
    \n
    \n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"],dependencies:[{kind:"component",type:be.MatChipListbox,selector:"mat-chip-listbox",inputs:["tabIndex","multiple","aria-orientation","selectable","compareWith","required","hideSingleSelectionIndicator","value"],outputs:["change"]},{kind:"component",type:be.MatChipOption,selector:"mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]",inputs:["color","disabled","disableRipple","tabIndex","selectable","selected"],outputs:["selectionChange"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Hn,decorators:[{type:n,args:[{selector:"tb-alarm-status-select",providers:[{provide:B,useExisting:a((()=>Hn)),multi:!0}],template:'
    \n \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.ACTIVE_ACK) | translate }}\n \n
    \n
    \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_UNACK) | translate }}\n \n \n {{ alarmStatusTranslations.get(alarmStatus.CLEARED_ACK) | translate }}\n \n
    \n
    \n
    \n',styles:[":host .chip-listbox{max-width:460px;width:100%}:host .chip-listbox .toggle-column{display:flex;flex:1 1 100%;gap:8px}:host .chip-listbox .option{margin:0}@media screen and (max-width: 959px){:host .chip-listbox{max-width:360px}:host .chip-listbox .toggle-column{flex-direction:column}}:host ::ng-deep .chip-listbox .mdc-evolution-chip-set__chips{gap:8px}:host ::ng-deep .chip-listbox .option button{flex-basis:100%;justify-content:start}:host ::ng-deep .chip-listbox .option .mdc-evolution-chip__graphic{flex-grow:0}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class jn{}e("RulenodeCoreConfigCommonModule",jn),jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,deps:[],target:t.ɵɵFactoryTarget.NgModule}),jn.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:jn,declarations:[An,Mn,En,Gn,Dn,In,Nn,_n,Cn,Bn,Kn,zn,Un,Hn,qn],imports:[$,M,_e],exports:[An,Mn,En,Gn,Dn,In,Nn,_n,Cn,Bn,Kn,zn,Un,Hn,qn]}),jn.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,imports:[$,M,_e]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:jn,decorators:[{type:c,args:[{declarations:[An,Mn,En,Gn,Dn,In,Nn,_n,Cn,Bn,Kn,zn,Un,Hn,qn],imports:[$,M,_e],exports:[An,Mn,En,Gn,Dn,In,Nn,_n,Cn,Bn,Kn,zn,Un,Hn,qn]}]}]});class $n{}e("RuleNodeCoreConfigActionModule",$n),$n.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,deps:[],target:t.ɵɵFactoryTarget.NgModule}),$n.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:$n,declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn],imports:[$,M,_e,jn],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn]}),$n.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,imports:[$,M,_e,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:$n,decorators:[{type:c,args:[{declarations:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn],imports:[$,M,_e,jn],exports:[Tn,ct,kn,vn,fn,ut,ft,gt,yt,yn,xt,ht,cn,gn,hn,Fn,Ln,bt,bn,xn,Sn]}]}]});class Qn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e.inputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],outputValueKey:[e.outputValueKey,[O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],useCache:[e.useCache,[]],addPeriodBetweenMsgs:[e.addPeriodBetweenMsgs,[]],periodValueKey:[e.periodValueKey,[]],round:[e.round,[O.min(0),O.max(15)]],tellFailureIfDeltaIsNegative:[e.tellFailureIfDeltaIsNegative,[]]})}prepareInputConfig(e){return{inputValueKey:ie(e?.inputValueKey)?e.inputValueKey:null,outputValueKey:ie(e?.outputValueKey)?e.outputValueKey:null,useCache:!ie(e?.useCache)||e.useCache,addPeriodBetweenMsgs:!!ie(e?.addPeriodBetweenMsgs)&&e.addPeriodBetweenMsgs,periodValueKey:ie(e?.periodValueKey)?e.periodValueKey:null,round:ie(e?.round)?e.round:null,tellFailureIfDeltaIsNegative:!ie(e?.tellFailureIfDeltaIsNegative)||e.tellFailureIfDeltaIsNegative}}prepareOutputConfig(e){return le(e)}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([O.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",Qn),Qn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Qn,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:"
    \n
    \n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
    \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
    \n
    \n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
    \n
    \n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
    \n
    \n
    \n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
    \n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
    \n
    \n
    \n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Qn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-calculate-delta-config",template:"
    \n
    \n \n {{ 'tb.rulenode.input-value-key' | translate }}\n \n \n {{ 'tb.rulenode.input-value-key-required' | translate }}\n \n \n \n {{ 'tb.rulenode.output-value-key' | translate }}\n \n \n {{ 'tb.rulenode.output-value-key-required' | translate }}\n \n \n
    \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n {{ 'tb.rulenode.number-of-digits-after-floating-point-range' | translate }}\n \n \n
    \n
    \n \n {{ 'tb.rulenode.failure-if-delta-negative' | translate }}\n \n
    \n
    \n \n {{ 'tb.rulenode.use-caching' | translate }}\n \n
    \n
    \n
    \n \n {{ 'tb.rulenode.add-time-difference-between-readings' | translate:\n { inputValueKey: calculateDeltaConfigForm.get('inputValueKey').valid ?\n calculateDeltaConfigForm.get('inputValueKey').value : 'tb.rulenode.input-value-key' | translate } }}\n \n
    \n \n {{ 'tb.rulenode.period-value-key' | translate }}\n \n \n {{ 'tb.rulenode.period-value-key-required' | translate }}\n \n \n
    \n
    \n
    \n"}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Jn extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.customerAttributesConfigForm}prepareOutputConfig(e){const t={};for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,le(e)}prepareInputConfig(e){let t,n;return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.customerAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo]})}}e("CustomerAttributesConfigComponent",Jn),Jn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Jn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Jn,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.mapping-of-customers
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:An,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Jn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
    \n
    tb.rulenode.mapping-of-customers
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Yn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e.deviceRelationsQuery,[O.required]],tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ie(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ie(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ie(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ie(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{deviceRelationsQuery:ie(e?.deviceRelationsQuery)?e.deviceRelationsQuery:null,tellFailureIfAbsent:!ie(e?.tellFailureIfAbsent)||e.tellFailureIfAbsent,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA,attributesControl:e?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("DeviceAttributesConfigComponent",Yn),Yn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Yn,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.device-relations-query
    \n \n \n
    \n
    \n
    \n
    tb.rulenode.related-device-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Mn,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Un,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Yn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
    \n
    \n
    tb.rulenode.device-relations-query
    \n \n \n
    \n
    \n
    \n
    tb.rulenode.related-device-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Wn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.predefinedValues=[];for(const e of Object.keys(Mt))this.predefinedValues.push({value:Mt[e],name:this.translate.instant(Et.get(Mt[e]))})}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){let t;return t=ie(e?.addToMetadata)?e.addToMetadata?en.METADATA:en.DATA:e?.fetchTo?e.fetchTo:en.DATA,{detailsList:ie(e?.detailsList)?e.detailsList:null,fetchTo:t}}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e.detailsList,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("EntityDetailsConfigComponent",Wn),Wn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Wn,selector:"tb-enrichment-node-entity-details-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n help\n \n \n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Wn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
    \n \n \n help\n \n \n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Zn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n,this.separatorKeysCodes=[ge,ye,xe],this.aggregationTypes=E,this.aggregations=Object.values(E),this.aggregationTypesTranslations=G,this.fetchMode=Gt,this.samplingOrders=Object.values(Vt),this.samplingOrdersTranslate=_t,this.timeUnits=Object.values(Nt),this.timeUnitsTranslationMap=St,this.deduplicationStrategiesHintTranslations=wt,this.headerOptions=[],this.timeUnitMap={[Nt.MILLISECONDS]:1,[Nt.SECONDS]:1e3,[Nt.MINUTES]:6e4,[Nt.HOURS]:36e5,[Nt.DAYS]:864e5},this.intervalValidator=()=>e=>e.get("startInterval").value*this.timeUnitMap[e.get("startIntervalTimeUnit").value]<=e.get("endInterval").value*this.timeUnitMap[e.get("endIntervalTimeUnit").value]?{intervalError:!0}:null;for(const e of Dt.keys())this.headerOptions.push({value:e,name:this.translate.instant(Dt.get(e))})}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e.latestTsKeyNames,[O.required]],aggregation:[e.aggregation,[O.required]],fetchMode:[e.fetchMode,[O.required]],orderBy:[e.orderBy,[]],limit:[e.limit,[]],useMetadataIntervalPatterns:[e.useMetadataIntervalPatterns,[]],interval:this.fb.group({startInterval:[e.interval.startInterval,[]],startIntervalTimeUnit:[e.interval.startIntervalTimeUnit,[]],endInterval:[e.interval.endInterval,[]],endIntervalTimeUnit:[e.interval.endIntervalTimeUnit,[]]}),startIntervalPattern:[e.startIntervalPattern,[]],endIntervalPattern:[e.endIntervalPattern,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}toggleChange(e){this.getTelemetryFromDatabaseConfigForm.get("fetchMode").patchValue(e,{emitEvent:!0})}prepareOutputConfig(e){return e.startInterval=e.interval.startInterval,e.startIntervalTimeUnit=e.interval.startIntervalTimeUnit,e.endInterval=e.interval.endInterval,e.endIntervalTimeUnit=e.interval.endIntervalTimeUnit,delete e.interval,le(e)}prepareInputConfig(e){return se(e)&&(e.interval={startInterval:e.startInterval,startIntervalTimeUnit:e.startIntervalTimeUnit,endInterval:e.endInterval,endIntervalTimeUnit:e.endIntervalTimeUnit}),{latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:null,aggregation:ie(e?.aggregation)?e.aggregation:E.NONE,fetchMode:ie(e?.fetchMode)?e.fetchMode:Gt.FIRST,orderBy:ie(e?.orderBy)?e.orderBy:Vt.ASC,limit:ie(e?.limit)?e.limit:1e3,useMetadataIntervalPatterns:!!ie(e?.useMetadataIntervalPatterns)&&e.useMetadataIntervalPatterns,interval:{startInterval:ie(e?.interval?.startInterval)?e.interval.startInterval:2,startIntervalTimeUnit:ie(e?.interval?.startIntervalTimeUnit)?e.interval.startIntervalTimeUnit:Nt.MINUTES,endInterval:ie(e?.interval?.endInterval)?e.interval.endInterval:1,endIntervalTimeUnit:ie(e?.interval?.endIntervalTimeUnit)?e.interval.endIntervalTimeUnit:Nt.MINUTES},startIntervalPattern:ie(e?.startIntervalPattern)?e.startIntervalPattern:null,endIntervalPattern:ie(e?.endIntervalPattern)?e.endIntervalPattern:null}}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Gt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([O.required,O.min(2),O.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([O.required,O.pattern(/(?:.|\s)*\S(&:.|\s)*/)])):(this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").setValidators([O.required,O.min(1),O.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").setValidators([O.required]),this.getTelemetryFromDatabaseConfigForm.get("interval").setValidators([this.intervalValidator()]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval.endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("interval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))}clearChipGrid(){this.getTelemetryFromDatabaseConfigForm.get("latestTsKeyNames").patchValue([],{emitEvent:!0})}addKey(e,t){const n=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}n&&(n.value="")}defaultPaddingEnable(){return this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value===Gt.ALL&&this.getTelemetryFromDatabaseConfigForm.get("aggregation").value===E.NONE}}e("GetTelemetryFromDatabaseConfigComponent",Zn),Zn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Zn,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
    \n \n
    \n help\n \n
    \n
    \n
    tb.rulenode.fetch-interval
    \n
    \n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
    \n
    \n
    \n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n error_outline\n
    \n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
    \n
    \n
    \n \n
    \n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
    \n
    \n
    \n
    \n
    tb.rulenode.fetch-strategy
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
    \n
    \n
    \n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
    \n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
    \n
    \n
    \n
    \n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Zn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
    \n \n
    \n help\n \n
    \n
    \n
    tb.rulenode.fetch-interval
    \n
    \n \n {{ \'tb.rulenode.use-metadata-dynamic-interval\' | translate }}\n \n
    \n
    \n
    \n \n {{ \'tb.rulenode.interval-start\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.interval-end\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n {{ \'tb.rulenode.time-unit\' | translate }}\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
    \n
    \n error_outline\n
    \n \n {{ \'tb.rulenode.fetch-timeseries-from-to\' | translate:\n {\n startInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.startInterval\').value,\n endInterval: getTelemetryFromDatabaseConfigForm.get(\'interval.endInterval\').value,\n startIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.startIntervalTimeUnit\').value.toLowerCase(),\n endIntervalTimeUnit: getTelemetryFromDatabaseConfigForm.get(\'interval.endIntervalTimeUnit\').value.toLowerCase()\n } }}\n \n \n {{ "tb.rulenode.fetch-timeseries-from-to-invalid" | translate }}\n \n
    \n
    \n
    \n \n
    \n \n {{ \'tb.rulenode.start-interval\' | translate }}\n \n \n {{ \'tb.rulenode.start-interval-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.end-interval\' | translate }}\n \n \n {{ \'tb.rulenode.end-interval-required\' | translate }}\n \n \n \n \n
    \n
    \n
    \n
    \n
    tb.rulenode.fetch-strategy
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n {{ deduplicationStrategiesHintTranslations.get(getTelemetryFromDatabaseConfigForm.get(\'fetchMode\').value) | translate }}\n
    \n
    \n
    \n \n {{ \'aggregation.function\' | translate }}\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n
    \n \n {{ "tb.rulenode.order-by-timestamp" | translate }} \n \n \n {{ samplingOrdersTranslate.get(order) | translate }}\n \n \n \n \n {{ "tb.rulenode.limit" | translate }}\n \n {{ "tb.rulenode.limit-hint" | translate }}\n \n {{ \'tb.rulenode.limit-required\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n {{ \'tb.rulenode.limit-range\' | translate }}\n \n \n
    \n
    \n
    \n
    \n',styles:[":host .see-example{display:inline-block}:host .description-block{display:flex;align-items:center;border-radius:6px;border:1px solid #EAEAEA}:host .description-block .description-icon{font-size:24px;height:24px;min-height:24px;width:24px;min-width:24px;line-height:24px;color:#d9d9d9;margin:4px}:host .description-block .description-text{font-size:12px;line-height:16px;letter-spacing:.25px;margin:6px}:host .description-block.error{color:var(--mdc-theme-error, #f44336)}:host .description-block.error .description-icon{color:var(--mdc-theme-error, #f44336)}:host .item-center{align-items:center}:host .item-center .fetch-mod-toggle{width:100%}:host .hint-container{width:100%}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class Xn extends f{constructor(e,t,n){super(e),this.store=e,this.translate=t,this.fb=n}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[e.tellFailureIfAbsent,[]],fetchTo:[e.fetchTo,[]],attributesControl:[e.attributesControl,[]]})}prepareInputConfig(e){return se(e)&&(e.attributesControl={clientAttributeNames:ie(e?.clientAttributeNames)?e.clientAttributeNames:[],latestTsKeyNames:ie(e?.latestTsKeyNames)?e.latestTsKeyNames:[],serverAttributeNames:ie(e?.serverAttributeNames)?e.serverAttributeNames:[],sharedAttributeNames:ie(e?.sharedAttributeNames)?e.sharedAttributeNames:[],getLatestValueWithTs:!!ie(e?.getLatestValueWithTs)&&e.getLatestValueWithTs}),{fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA,tellFailureIfAbsent:!!ie(e?.tellFailureIfAbsent)&&e.tellFailureIfAbsent,attributesControl:ie(e?.attributesControl)?e.attributesControl:null}}prepareOutputConfig(e){for(const t of Object.keys(e.attributesControl))e[t]=e.attributesControl[t];return delete e.attributesControl,e}}e("OriginatorAttributesConfigComponent",Xn),Xn.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,deps:[{token:P.Store},{token:Z.TranslateService},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xn.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Xn,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    \n
    tb.rulenode.originator-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Un,selector:"tb-select-attributes",inputs:["popupHelpLink"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Xn,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
    \n
    \n
    \n
    tb.rulenode.originator-attributes
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n \n \n \n
    \n
    \n \n {{ \'tb.rulenode.tell-failure\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:Z.TranslateService},{type:R.FormBuilder}]}});class er extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.originatorFields=[];for(const e of kt)this.originatorFields.push({value:e.value,name:this.translate.instant(e.name)})}configForm(){return this.originatorFieldsConfigForm}prepareOutputConfig(e){return le(e)}prepareInputConfig(e){return{dataMapping:ie(e?.dataMapping)?e.dataMapping:null,ignoreNullStrings:ie(e?.ignoreNullStrings)?e.ignoreNullStrings:null,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({dataMapping:[e.dataMapping,[O.required]],ignoreNullStrings:[e.ignoreNullStrings,[]],fetchTo:[e.fetchTo,[]]})}}e("OriginatorFieldsConfigComponent",er),er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),er.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:er,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n
    \n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
    \n
    \n',dependencies:[{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Kn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:er,decorators:[{type:n,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
    \n \n \n \n \n
    \n \n {{ \'tb.rulenode.skip-empty-fields\' | translate }}\n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class tr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.DataToFetch=Pt,this.msgMetadataLabelTranslations=Ot,this.originatorFields=[],this.fetchToData=[];for(const e of Object.keys(kt))this.originatorFields.push({value:kt[e].value,name:this.translate.instant(kt[e].name)});for(const e of Rt.keys())this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.relatedAttributesConfigForm}prepareOutputConfig(e){e.dataToFetch===Pt.FIELDS?(e.dataMapping=e.svMap,delete e.svMap):(e.dataMapping=e.kvMap,delete e.kvMap);const t={};if(e&&e.dataMapping)for(const n of Object.keys(e.dataMapping))t[n.trim()]=e.dataMapping[n];return e.dataMapping=t,delete e.svMap,delete e.kvMap,le(e)}prepareInputConfig(e){let t,n,r={[k.name.value]:`relatedEntity${this.translate.instant(k.name.name)}`},o={serialNumber:"sn"};return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,t===Pt.FIELDS?r=n:o=n,{relationsQuery:ie(e?.relationsQuery)?e.relationsQuery:null,dataToFetch:t,svMap:r,kvMap:o,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e.relationsQuery,[O.required]],dataToFetch:[e.dataToFetch,[]],kvMap:[e.kvMap,[O.required]],svMap:[e.svMap,[O.required]],fetchTo:[e.fetchTo,[]]})}validatorTriggers(){return["dataToFetch"]}updateValidators(e){this.relatedAttributesConfigForm.get("dataToFetch").value===Pt.FIELDS?(this.relatedAttributesConfigForm.get("svMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("svMap").updateValueAndValidity()):(this.relatedAttributesConfigForm.get("svMap").disable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").enable({emitEvent:!1}),this.relatedAttributesConfigForm.get("kvMap").updateValueAndValidity())}}e("RelatedAttributesConfigComponent",tr),tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:tr,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n
    tb.rulenode.data-to-fetch
    \n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:An,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:En,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"component",type:Kn,selector:"tb-sv-map-config",inputs:["selectOptions","disabled","labelText","requiredText","targetKeyPrefix","selectText","selectRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:tr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
    \n \n \n
    \n
    tb.rulenode.data-to-fetch
    \n \n \n {{ data.name }}\n \n \n \n \n \n \n \n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class nr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.fetchToData=[],this.DataToFetch=Pt;for(const e of Rt.keys())e!==Pt.FIELDS&&this.fetchToData.push({value:e,name:this.translate.instant(Rt.get(e))})}configForm(){return this.tenantAttributesConfigForm}prepareInputConfig(e){let t,n;return t=ie(e?.telemetry)?e.telemetry?Pt.LATEST_TELEMETRY:Pt.ATTRIBUTES:ie(e?.dataToFetch)?e.dataToFetch:Pt.ATTRIBUTES,n=ie(e?.attrMapping)?e.attrMapping:ie(e?.dataMapping)?e.dataMapping:null,{dataToFetch:t,dataMapping:n,fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}selectTranslation(e,t){return this.tenantAttributesConfigForm.get("dataToFetch").value===Pt.LATEST_TELEMETRY?e:t}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({dataToFetch:[e.dataToFetch,[]],dataMapping:[e.dataMapping,[O.required]],fetchTo:[e.fetchTo,[]]})}}e("TenantAttributesConfigComponent",nr),nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:nr,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.mapping-of-tenant
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:W.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:An,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:nr,decorators:[{type:n,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
    \n
    tb.rulenode.mapping-of-tenant
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class rr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}prepareInputConfig(e){return{fetchTo:ie(e?.fetchTo)?e.fetchTo:en.METADATA}}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchTo:[e.fetchTo,[]]})}}e("FetchDeviceCredentialsConfigComponent",rr),rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:rr,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:rr,decorators:[{type:n,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class or{}e("RulenodeCoreConfigEnrichmentModule",or),or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,deps:[],target:t.ɵɵFactoryTarget.NgModule}),or.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:or,declarations:[Jn,Wn,Yn,Xn,er,Zn,tr,nr,Qn,rr],imports:[$,M,jn],exports:[Jn,Wn,Yn,Xn,er,Zn,tr,nr,Qn,rr]}),or.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,imports:[$,M,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:or,decorators:[{type:c,args:[{declarations:[Jn,Wn,Yn,Xn,er,Zn,tr,nr,Qn,rr],imports:[$,M,jn],exports:[Jn,Wn,Yn,Xn,er,Zn,tr,nr,Qn,rr]}]}]});class ar extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Ht,this.azureIotHubCredentialsTypeTranslationsMap=jt}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[O.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[O.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),n=t.get("type").value;switch(e&&t.reset({type:n},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),n){case"sas":t.get("sasKey").setValidators([O.required]);break;case"cert.PEM":t.get("privateKey").setValidators([O.required]),t.get("privateKeyFileName").setValidators([O.required]),t.get("cert").setValidators([O.required]),t.get("certFileName").setValidators([O.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",ar),ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ar.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ar,selector:"tb-external-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
    \n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:H.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{kind:"directive",type:H.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:We.MatAccordion,selector:"mat-accordion",inputs:["multi","hideToggle","displayMode","togglePosition"],exportAs:["matAccordion"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:We.MatExpansionPanelDescription,selector:"mat-panel-description"},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:R.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ar,decorators:[{type:n,args:[{selector:"tb-external-node-azure-iot-hub-config",template:'
    \n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
    \n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
    \n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
    \n
    \n
    \n
    \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ir extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Qt,this.ToByteStandartCharsetTypeTranslationMap=Jt}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],keyPattern:[e?e.keyPattern:null],bootstrapServers:[e?e.bootstrapServers:null,[O.required]],retries:[e?e.retries:null,[O.min(0)]],batchSize:[e?e.batchSize:null,[O.min(0)]],linger:[e?e.linger:null,[O.min(0)]],bufferMemory:[e?e.bufferMemory:null,[O.min(0)]],acks:[e?e.acks:null,[O.required]],keySerializer:[e?e.keySerializer:null,[O.required]],valueSerializer:[e?e.valueSerializer:null,[O.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([O.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",ir),ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ir.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ir,selector:"tb-external-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
    tb.rulenode.key-pattern-hint
    \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
    tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
    \n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ir,decorators:[{type:n,args:[{selector:"tb-external-node-kafka-config",template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.key-pattern\n \n tb.rulenode.general-pattern-hint\n \n
    tb.rulenode.key-pattern-hint
    \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
    tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
    \n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class lr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[O.required]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[O.required,O.min(1),O.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&me(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],retainedMessage:[!!e&&e.retainedMessage,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})}updateValidators(e){me(this.mqttConfigForm.get("clientId").value)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1}),this.mqttConfigForm.get("appendClientIdSuffix").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["clientId"]}}e("MqttConfigComponent",lr),lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:lr,selector:"tb-external-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
    \n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
    {{ "tb.rulenode.client-id-suffix-hint" | translate }}
    \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"],dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Dn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:lr,decorators:[{type:n,args:[{selector:"tb-external-node-mqtt-config",template:'
    \n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
    \n \n tb.rulenode.client-id\n \n {{\'tb.rulenode.client-id-hint\' | translate}}\n \n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
    {{ "tb.rulenode.client-id-suffix-hint" | translate }}
    \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
    \n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class sr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.notificationType=D,this.entityType=F}configForm(){return this.notificationConfigForm}onConfigurationSet(e){this.notificationConfigForm=this.fb.group({templateId:[e?e.templateId:null,[O.required]],targets:[e?e.targets:[],[O.required]]})}}e("NotificationConfigComponent",sr),sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:sr,selector:"tb-external-node-notification-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n
    \n',dependencies:[{kind:"component",type:tt.EntityListComponent,selector:"tb-entity-list",inputs:["entityType","subType","labelText","placeholderText","requiredText","required","disabled","subscriptSizing","hint"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:nt.TemplateAutocompleteComponent,selector:"tb-template-autocomplete",inputs:["required","allowCreate","allowEdit","disabled","notificationTypes"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:sr,decorators:[{type:n,args:[{selector:"tb-external-node-notification-config",template:'
    \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class mr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[O.required]],topicName:[e?e.topicName:null,[O.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[O.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[O.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",mr),mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),mr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:mr,selector:"tb-external-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
    \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Ze.FileInputComponent,selector:"tb-file-input",inputs:["label","hint","accept","noFileText","inputId","allowedExtensions","dropLabel","maxSizeByte","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:mr,decorators:[{type:n,args:[{selector:"tb-external-node-pub-sub-config",template:'
    \n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[O.required]],port:[e?e.port:null,[O.required,O.min(1),O.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[O.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[O.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",pr),pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:pr,selector:"tb-external-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:pr,decorators:[{type:n,args:[{selector:"tb-external-node-rabbit-mq-config",template:'
    \n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
    \n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class dr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys($t)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[O.required]],requestMethod:[e?e.requestMethod:null,[O.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],parseToPlainText:[!!e&&e.parseToPlainText,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[O.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,n=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,o=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!o?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[O.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[O.required,O.min(1),O.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([O.min(0)])),n?this.restApiCallConfigForm.get("maxQueueSize").setValidators([O.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",dr),dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:dr,selector:"tb-external-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
    tb.rulenode.parse-to-plain-text-hint
    \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n
    \n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
    \n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
    \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Dn,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRequired"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:dr,decorators:[{type:n,args:[{selector:"tb-external-node-rest-api-call-config",template:'
    \n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.parse-to-plain-text\' | translate }}\n \n
    tb.rulenode.parse-to-plain-text-hint
    \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n
    \n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
    \n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
    \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class ur extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,n=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([O.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([O.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([O.required,O.min(1),O.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([O.required,O.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(n?[O.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(n?[O.required,O.min(1),O.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",ur),ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),ur.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:ur,selector:"tb-external-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
    \n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
    \n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:rt.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Xe.TogglePasswordComponent,selector:"tb-toggle-password"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:ur,decorators:[{type:n,args:[{selector:"tb-external-node-send-email-config",template:'
    \n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
    \n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
    \n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
    \n
    \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
    \n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
    \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[O.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[O.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([O.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",cr),cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:cr,selector:"tb-external-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ot.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:cr,decorators:[{type:n,args:[{selector:"tb-external-node-send-sms-config",template:'
    \n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.slackChanelTypes=Object.keys(w),this.slackChanelTypesTranslateMap=V}configForm(){return this.slackConfigForm}onConfigurationSet(e){this.slackConfigForm=this.fb.group({botToken:[e?e.botToken:null],useSystemSettings:[!!e&&e.useSystemSettings],messageTemplate:[e?e.messageTemplate:null,[O.required]],conversationType:[e?e.conversationType:null,[O.required]],conversation:[e?e.conversation:null,[O.required]]})}validatorTriggers(){return["useSystemSettings"]}updateValidators(e){this.slackConfigForm.get("useSystemSettings").value?this.slackConfigForm.get("botToken").clearValidators():this.slackConfigForm.get("botToken").setValidators([O.required]),this.slackConfigForm.get("botToken").updateValueAndValidity({emitEvent:e})}}e("SlackConfigComponent",fr),fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:fr,selector:"tb-external-node-slack-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
    \n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:Q.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex"],exportAs:["matCheckbox"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:at.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{kind:"component",type:at.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:it.SlackConversationAutocompleteComponent,selector:"tb-slack-conversation-autocomplete",inputs:["labelText","requiredText","required","disabled","slackChanelType","token"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:fr,decorators:[{type:n,args:[{selector:"tb-external-node-slack-config",template:'
    \n \n tb.rulenode.message-template\n \n \n {{ \'tb.rulenode.message-template-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n {{ \'tb.rulenode.use-system-slack-settings\' | translate }}\n \n \n tb.rulenode.slack-api-token\n \n \n {{ \'tb.rulenode.slack-api-token-required\' | translate }}\n \n \n \n \n \n {{ slackChanelTypesTranslateMap.get(slackChanelType) | translate }}\n \n \n \n \n
    \n',styles:[":host .tb-title{display:block;padding-bottom:6px}:host ::ng-deep .mat-mdc-radio-group{display:flex;flex-direction:row;margin-bottom:22px;gap:12px}:host ::ng-deep .mat-mdc-radio-group .mat-mdc-radio-button{flex:1 1 100%;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:6px}@media screen and (max-width: 599px){:host ::ng-deep .mat-mdc-radio-group{flex-direction:column}}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class gr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[O.required]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SnsConfigComponent",gr),gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),gr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:gr,selector:"tb-external-node-sns-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:gr,decorators:[{type:n,args:[{selector:"tb-external-node-sns-config",template:'
    \n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class yr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Bt,this.sqsQueueTypes=Object.keys(Bt),this.sqsQueueTypeTranslationsMap=Kt}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[O.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[O.required]],delaySeconds:[e?e.delaySeconds:null,[O.min(0),O.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[O.required]],secretAccessKey:[e?e.secretAccessKey:null,[O.required]],region:[e?e.region:null,[O.required]]})}}e("SqsConfigComponent",yr),yr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),yr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:yr,selector:"tb-external-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Cn,selector:"tb-kv-map-config-old",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{kind:"pipe",type:he.SafePipe,name:"safe"},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:yr,decorators:[{type:n,args:[{selector:"tb-external-node-sqs-config",template:'
    \n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n tb.rulenode.general-pattern-hint\n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
    \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class xr{}e("RulenodeCoreConfigExternalModule",xr),xr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),xr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:xr,declarations:[gr,yr,mr,ir,lr,sr,pr,dr,ur,ar,cr,fr],imports:[$,M,_e,jn],exports:[gr,yr,mr,ir,lr,sr,pr,dr,ur,ar,cr,fr]}),xr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,imports:[$,M,_e,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:xr,decorators:[{type:c,args:[{declarations:[gr,yr,mr,ir,lr,sr,pr,dr,ur,ar,cr,fr],imports:[$,M,_e,jn],exports:[gr,yr,mr,ir,lr,sr,pr,dr,ur,ar,cr,fr]}]}]});class br extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.searchText=""}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return{alarmStatusList:ie(e?.alarmStatusList)?e.alarmStatusList:null}}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e.alarmStatusList,[O.required]]})}}e("CheckAlarmStatusComponent",br),br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),br.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:br,selector:"tb-filter-node-check-alarm-status-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.alarm-status
    \n
    \n tb.rulenode.alarm-required\n
    \n
    \n \n
    \n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:Hn,selector:"tb-alarm-status-select"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:br,decorators:[{type:n,args:[{selector:"tb-filter-node-check-alarm-status-config",template:'
    \n
    \n
    tb.rulenode.alarm-status
    \n
    \n tb.rulenode.alarm-required\n
    \n
    \n \n
    \n\n\n\n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class hr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.checkMessageConfigForm}prepareInputConfig(e){return{messageNames:ie(e?.messageNames)?e.messageNames:[],metadataNames:ie(e?.metadataNames)?e.metadataNames:[],checkAllKeys:!!ie(e?.checkAllKeys)&&e.checkAllKeys}}prepareOutputConfig(e){return{messageNames:ie(e?.messageNames)?e.messageNames:[],metadataNames:ie(e?.metadataNames)?e.metadataNames:[],checkAllKeys:e.checkAllKeys}}atLeastOne(e,t=null){return n=>{t||(t=Object.keys(n.controls));return n?.controls&&t.some((t=>!e(n.controls[t])))?null:{atLeastOne:!0}}}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e.messageNames,[]],metadataNames:[e.metadataNames,[]],checkAllKeys:[e.checkAllKeys,[]]},{validators:this.atLeastOne(O.required,["messageNames","metadataNames"])})}get touchedValidationControl(){return["messageNames","metadataNames"].some((e=>this.checkMessageConfigForm.get(e).touched))}}e("CheckMessageConfigComponent",hr),hr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),hr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:hr,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.fields-to-check
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n help\n \n \n help\n \n
    \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:hr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-message-config",template:'
    \n
    \n
    tb.rulenode.fields-to-check
    \n
    \n tb.rulenode.at-least-one-field-required\n
    \n
    \n \n help\n \n \n help\n \n
    \n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class vr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.values(v),this.entitySearchDirectionTranslationsMap=C}configForm(){return this.checkRelationConfigForm}prepareInputConfig(e){return{checkForSingleEntity:!!ie(e?.checkForSingleEntity)&&e.checkForSingleEntity,direction:ie(e?.direction)?e.direction:null,entityType:ie(e?.entityType)?e.entityType:null,entityId:ie(e?.entityId)?e.entityId:null,relationType:ie(e?.relationType)?e.relationType:null}}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[e.checkForSingleEntity,[]],direction:[e.direction,[]],entityType:[e.entityType,e&&e.checkForSingleEntity?[O.required]:[]],entityId:[e.entityId,e&&e.checkForSingleEntity?[O.required]:[]],relationType:[e.relationType,[O.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[O.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",vr),vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:vr,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.relation-search-parameters
    \n
    \n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
    \n
    \n \n \n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"component",type:Je.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["showLabel","additionalClasses","appearance","required","disabled","subscriptSizing"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:vr,decorators:[{type:n,args:[{selector:"tb-filter-node-check-relation-config",template:'
    \n
    tb.rulenode.relation-search-parameters
    \n
    \n \n {{ \'relation.direction\' | translate }}\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }} tb.rulenode.relations-query-config-direction-suffix\n \n \n \n \n \n
    \n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
    \n
    \n \n \n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Cr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Tt,this.perimeterTypes=Object.values(Tt),this.perimeterTypeTranslationMap=It,this.rangeUnits=Object.values(qt),this.rangeUnitTranslationMap=At,this.defaultPaddingEnable=!0}configForm(){return this.geoFilterConfigForm}prepareInputConfig(e){return{latitudeKeyName:ie(e?.latitudeKeyName)?e.latitudeKeyName:null,longitudeKeyName:ie(e?.longitudeKeyName)?e.longitudeKeyName:null,perimeterType:ie(e?.perimeterType)?e.perimeterType:null,fetchPerimeterInfoFromMessageMetadata:!!ie(e?.fetchPerimeterInfoFromMessageMetadata)&&e.fetchPerimeterInfoFromMessageMetadata,perimeterKeyName:ie(e?.perimeterKeyName)?e.perimeterKeyName:null,centerLatitude:ie(e?.centerLatitude)?e.centerLatitude:null,centerLongitude:ie(e?.centerLongitude)?e.centerLongitude:null,range:ie(e?.range)?e.range:null,rangeUnit:ie(e?.rangeUnit)?e.rangeUnit:null,polygonsDefinition:ie(e?.polygonsDefinition)?e.polygonsDefinition:null}}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e.latitudeKeyName,[O.required]],longitudeKeyName:[e.longitudeKeyName,[O.required]],perimeterType:[e.perimeterType,[O.required]],fetchPerimeterInfoFromMessageMetadata:[e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e.perimeterKeyName,[]],centerLatitude:[e.centerLatitude,[]],centerLongitude:[e.centerLongitude,[]],range:[e.range,[]],rangeUnit:[e.rangeUnit,[]],polygonsDefinition:[e.polygonsDefinition,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([O.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||n!==Tt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([]),this.defaultPaddingEnable=!0):(this.geoFilterConfigForm.get("centerLatitude").setValidators([O.required,O.min(-90),O.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([O.required,O.min(-180),O.max(180)]),this.geoFilterConfigForm.get("range").setValidators([O.required,O.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([O.required]),this.defaultPaddingEnable=!1),t||n!==Tt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([O.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",Cr),Cr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Cr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Cr,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"component",type:te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex"],exportAs:["matSlideToggle"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:W.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{kind:"directive",type:R.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:ne.HintTooltipIconComponent,selector:"[tb-hint-tooltip-icon]",inputs:["tb-hint-tooltip-icon","tooltipPosition","hintIcon"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Cr,decorators:[{type:n,args:[{selector:"tb-filter-node-gps-geofencing-config",template:'
    \n
    \n
    tb.rulenode.coordinate-field-names
    \n
    \n
    \n \n {{ \'tb.rulenode.latitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.latitude-field-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.longitude-field-name\' | translate }}\n \n \n {{ \'tb.rulenode.longitude-field-name-required\' | translate }}\n \n \n
    \n
    tb.rulenode.coordinate-field-hint
    \n
    \n
    \n
    \n
    tb.rulenode.geofence-configuration
    \n
    \n \n {{ \'tb.rulenode.perimeter-type\' | translate }}\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
    \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-metadata\' | translate }}\n \n
    \n \n {{ \'tb.rulenode.perimeter-key-name\' | translate }}\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n {{ \'tb.rulenode.perimeter-key-name-hint\' | translate }}\n \n
    \n
    \n \n {{ \'tb.rulenode.circle-center-latitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.circle-center-longitude\' | translate }}\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.range\' | translate }}\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.range-units\' | translate }}\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n {{ \'tb.rulenode.range-units-required\' | translate }}\n \n \n
    \n
    \n \n {{ \'tb.rulenode.polygon-definition\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-hint\' | translate }}\n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .slide-toggle{margin-bottom:18px}\n",':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Fr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}prepareInputConfig(e){return{messageTypes:ie(e?.messageTypes)?e.messageTypes:null}}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e.messageTypes,[O.required]]})}}e("MessageTypeConfigComponent",Fr),Fr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Fr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Fr,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Gn,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Fr,decorators:[{type:n,args:[{selector:"tb-filter-node-message-type-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.TENANT,F.CUSTOMER,F.USER,F.DASHBOARD,F.RULE_CHAIN,F.RULE_NODE,F.EDGE]}configForm(){return this.originatorTypeConfigForm}prepareInputConfig(e){return{originatorTypes:ie(e?.originatorTypes)?e.originatorTypes:null}}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e.originatorTypes,[O.required]]})}}e("OriginatorTypeConfigComponent",kr),kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:kr,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
    \n \n help\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:st.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","additionalClasses","appearance","label","floatLabel","disabled","subscriptSizing","allowedEntityTypes","emptyInputPlaceholder","filledInputPlaceholder","ignoreAuthorityFilter"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:W.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:kr,decorators:[{type:n,args:[{selector:"tb-filter-node-originator-type-config",template:'
    \n \n help\n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Lr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-filter-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ie(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ie(e?.jsScript)?e.jsScript:null,tbelScript:ie(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Lr),Lr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Lr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Lr,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Lr,decorators:[{type:n,args:[{selector:"tb-filter-node-script-config",template:'
    \n \n \n \n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Tr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-switch-function"}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({scriptLang:[e.scriptLang,[O.required]],jsScript:[e.jsScript,[]],tbelScript:[e.tbelScript,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.switchConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.switchConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.switchConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.switchConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),{scriptLang:ie(e?.scriptLang)?e.scriptLang:x.JS,jsScript:ie(e?.jsScript)?e.jsScript:null,tbelScript:ie(e?.tbelScript)?e.tbelScript:null}}testScript(e){const t=this.switchConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",o=this.switchConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.switchConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.switchConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",Tr),Tr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,deps:[{token:P.Store},{token:R.UntypedFormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Tr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Tr,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Tr,decorators:[{type:n,args:[{selector:"tb-filter-node-switch-config",template:'
    \n \n \n \n \n \n \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}});class Ir{}e("RuleNodeCoreConfigFilterModule",Ir),Ir.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Ir.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Ir,declarations:[hr,vr,Cr,Fr,kr,Lr,Tr,br],imports:[$,M,jn],exports:[hr,vr,Cr,Fr,kr,Lr,Tr,br]}),Ir.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,imports:[$,M,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ir,decorators:[{type:c,args:[{declarations:[hr,vr,Cr,Fr,kr,Lr,Tr,br],imports:[$,M,jn],exports:[hr,vr,Cr,Fr,kr,Lr,Tr,br]}]}]});class Nr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=vt,this.originatorSources=Object.keys(vt),this.originatorSourceTranslationMap=Ct,this.originatorSourceDescTranslationMap=Ft,this.allowedEntityTypes=[F.DEVICE,F.ASSET,F.ENTITY_VIEW,F.USER,F.EDGE]}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[O.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===vt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([O.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===vt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([O.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([O.required,O.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Nr),Nr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Nr,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
    \n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
    \n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
    \n
    \n
    \n
    \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
    \n
    \n \n \n
    \n',dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ve.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:En,selector:"tb-relations-query-config",inputs:["disabled","required"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Nr,decorators:[{type:n,args:[{selector:"tb-transformation-node-change-originator-config",template:'
    \n \n tb.rulenode.new-originator\n \n \n \n {{ originatorSourceTranslationMap.get(changeOriginatorConfigForm.get(\'originatorSource\').value) | translate }}\n \n \n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n
    \n \n {{ originatorSourceDescTranslationMap.get(source) | translate }}\n \n
    \n
    \n
    \n
    \n \n \n
    \n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n
    \n
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Sr extends f{constructor(e,t,n,o){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=n,this.translate=o,this.tbelEnabled=re(this.store).tbelEnabled,this.scriptLanguage=x,this.changeScript=new r,this.hasScript=!0,this.testScriptLabel="tb.rulenode.test-transformer-function"}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({scriptLang:[e?e.scriptLang:x.JS,[O.required]],jsScript:[e?e.jsScript:null,[O.required]],tbelScript:[e?e.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(e){let t=this.scriptConfigForm.get("scriptLang").value;t!==x.TBEL||this.tbelEnabled||(t=x.JS,this.scriptConfigForm.get("scriptLang").patchValue(t,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(t===x.JS?[O.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:e}),this.scriptConfigForm.get("tbelScript").setValidators(t===x.TBEL?[O.required]:[]),this.scriptConfigForm.get("tbelScript").updateValueAndValidity({emitEvent:e})}prepareInputConfig(e){return e&&(e.scriptLang||(e.scriptLang=x.JS)),e}testScript(e){const t=this.scriptConfigForm.get("scriptLang").value,n=t===x.JS?"jsScript":"tbelScript",r=t===x.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",o=this.scriptConfigForm.get(n).value;this.nodeScriptTestService.testNodeScript(o,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,r,t,e).subscribe((e=>{e&&(this.scriptConfigForm.get(n).setValue(e),this.changeScript.emit())}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===x.JS&&this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",Sr),Sr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,deps:[{token:P.Store},{token:R.FormBuilder},{token:oe.NodeScriptTestService},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Sr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Sr,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n',dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:pe.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","scriptLanguage","noValidate","required"]},{kind:"component",type:de.MatButton,selector:" button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:de.MatIconButton,selector:"button[mat-icon-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:fe.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Sr,decorators:[{type:n,args:[{selector:"tb-transformation-node-script-config",template:'
    \n \n \n \n \n \n \n \n
    \n \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:oe.NodeScriptTestService},{type:Z.TranslateService}]},propDecorators:{jsFuncComponent:[{type:o,args:["jsFuncComponent",{static:!1}]}],tbelFuncComponent:[{type:o,args:["tbelFuncComponent",{static:!1}]}]}}); + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + const qr=mt({passive:!0});class Ar{constructor(e,t){this._platform=e,this._ngZone=t,this._monitoredElements=new Map}monitor(e){if(!this._platform.isBrowser)return He;const t=Te(e),n=this._monitoredElements.get(t);if(n)return n.subject;const r=new Ke,o="cdk-text-field-autofilled",a=e=>{"cdk-text-field-autofill-start"!==e.animationName||t.classList.contains(o)?"cdk-text-field-autofill-end"===e.animationName&&t.classList.contains(o)&&(t.classList.remove(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!1})))):(t.classList.add(o),this._ngZone.run((()=>r.next({target:e.target,isAutofilled:!0}))))};return this._ngZone.runOutsideAngular((()=>{t.addEventListener("animationstart",a,qr),t.classList.add("cdk-text-field-autofill-monitored")})),this._monitoredElements.set(t,{subject:r,unlisten:()=>{t.removeEventListener("animationstart",a,qr)}}),r}stopMonitoring(e){const t=Te(e),n=this._monitoredElements.get(t);n&&(n.unlisten(),n.subject.complete(),t.classList.remove("cdk-text-field-autofill-monitored"),t.classList.remove("cdk-text-field-autofilled"),this._monitoredElements.delete(t))}ngOnDestroy(){this._monitoredElements.forEach(((e,t)=>this.stopMonitoring(t)))}}Ar.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Ar,deps:[{token:pt.Platform},{token:t.NgZone}],target:t.ɵɵFactoryTarget.Injectable}),Ar.ɵprov=t.ɵɵngDeclareInjectable({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Ar,providedIn:"root"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Ar,decorators:[{type:s,args:[{providedIn:"root"}]}],ctorParameters:function(){return[{type:pt.Platform},{type:t.NgZone}]}});class Mr{constructor(e,t){this._elementRef=e,this._autofillMonitor=t,this.cdkAutofill=new r}ngOnInit(){this._autofillMonitor.monitor(this._elementRef).subscribe((e=>this.cdkAutofill.emit(e)))}ngOnDestroy(){this._autofillMonitor.stopMonitoring(this._elementRef)}}Mr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,deps:[{token:t.ElementRef},{token:Ar}],target:t.ɵɵFactoryTarget.Directive}),Mr.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Mr,selector:"[cdkAutofill]",outputs:{cdkAutofill:"cdkAutofill"},ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Mr,decorators:[{type:d,args:[{selector:"[cdkAutofill]"}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:Ar}]},propDecorators:{cdkAutofill:[{type:u}]}}); + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + class Er{get minRows(){return this._minRows}set minRows(e){this._minRows=Ie(e),this._setMinHeight()}get maxRows(){return this._maxRows}set maxRows(e){this._maxRows=Ie(e),this._setMaxHeight()}get enabled(){return this._enabled}set enabled(e){e=Le(e),this._enabled!==e&&((this._enabled=e)?this.resizeToFitContent(!0):this.reset())}get placeholder(){return this._textareaElement.placeholder}set placeholder(e){this._cachedPlaceholderHeight=void 0,e?this._textareaElement.setAttribute("placeholder",e):this._textareaElement.removeAttribute("placeholder"),this._cacheTextareaPlaceholderHeight()}constructor(e,t,n,r){this._elementRef=e,this._platform=t,this._ngZone=n,this._destroyed=new Ke,this._enabled=!0,this._previousMinRows=-1,this._isViewInited=!1,this._handleFocusEvent=e=>{this._hasFocus="focus"===e.type},this._document=r,this._textareaElement=this._elementRef.nativeElement}_setMinHeight(){const e=this.minRows&&this._cachedLineHeight?this.minRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.minHeight=e)}_setMaxHeight(){const e=this.maxRows&&this._cachedLineHeight?this.maxRows*this._cachedLineHeight+"px":null;e&&(this._textareaElement.style.maxHeight=e)}ngAfterViewInit(){this._platform.isBrowser&&(this._initialHeight=this._textareaElement.style.height,this.resizeToFitContent(),this._ngZone.runOutsideAngular((()=>{const e=this._getWindow();je(e,"resize").pipe(Pe(16),Ve(this._destroyed)).subscribe((()=>this.resizeToFitContent(!0))),this._textareaElement.addEventListener("focus",this._handleFocusEvent),this._textareaElement.addEventListener("blur",this._handleFocusEvent)})),this._isViewInited=!0,this.resizeToFitContent(!0))}ngOnDestroy(){this._textareaElement.removeEventListener("focus",this._handleFocusEvent),this._textareaElement.removeEventListener("blur",this._handleFocusEvent),this._destroyed.next(),this._destroyed.complete()}_cacheTextareaLineHeight(){if(this._cachedLineHeight)return;let e=this._textareaElement.cloneNode(!1);e.rows=1,e.style.position="absolute",e.style.visibility="hidden",e.style.border="none",e.style.padding="0",e.style.height="",e.style.minHeight="",e.style.maxHeight="",e.style.overflow="hidden",this._textareaElement.parentNode.appendChild(e),this._cachedLineHeight=e.clientHeight,e.remove(),this._setMinHeight(),this._setMaxHeight()}_measureScrollHeight(){const e=this._textareaElement,t=e.style.marginBottom||"",n=this._platform.FIREFOX,r=n&&this._hasFocus,o=n?"cdk-textarea-autosize-measuring-firefox":"cdk-textarea-autosize-measuring";r&&(e.style.marginBottom=`${e.clientHeight}px`),e.classList.add(o);const a=e.scrollHeight-4;return e.classList.remove(o),r&&(e.style.marginBottom=t),a}_cacheTextareaPlaceholderHeight(){if(!this._isViewInited||null!=this._cachedPlaceholderHeight)return;if(!this.placeholder)return void(this._cachedPlaceholderHeight=0);const e=this._textareaElement.value;this._textareaElement.value=this._textareaElement.placeholder,this._cachedPlaceholderHeight=this._measureScrollHeight(),this._textareaElement.value=e}ngDoCheck(){this._platform.isBrowser&&this.resizeToFitContent()}resizeToFitContent(e=!1){if(!this._enabled)return;if(this._cacheTextareaLineHeight(),this._cacheTextareaPlaceholderHeight(),!this._cachedLineHeight)return;const t=this._elementRef.nativeElement,n=t.value;if(!e&&this._minRows===this._previousMinRows&&n===this._previousValue)return;const r=this._measureScrollHeight(),o=Math.max(r,this._cachedPlaceholderHeight||0);t.style.height=`${o}px`,this._ngZone.runOutsideAngular((()=>{"undefined"!=typeof requestAnimationFrame?requestAnimationFrame((()=>this._scrollToCaretPosition(t))):setTimeout((()=>this._scrollToCaretPosition(t)))})),this._previousValue=n,this._previousMinRows=this._minRows}reset(){void 0!==this._initialHeight&&(this._textareaElement.style.height=this._initialHeight)}_noopInputHandler(){}_getDocument(){return this._document||document}_getWindow(){return this._getDocument().defaultView||window}_scrollToCaretPosition(e){const{selectionStart:t,selectionEnd:n}=e;!this._destroyed.isStopped&&this._hasFocus&&e.setSelectionRange(t,n)}}Er.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,deps:[{token:t.ElementRef},{token:pt.Platform},{token:t.NgZone},{token:j,optional:!0}],target:t.ɵɵFactoryTarget.Directive}),Er.ɵdir=t.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"15.2.0-rc.0",type:Er,selector:"textarea[cdkTextareaAutosize]",inputs:{minRows:["cdkAutosizeMinRows","minRows"],maxRows:["cdkAutosizeMaxRows","maxRows"],enabled:["cdkTextareaAutosize","enabled"],placeholder:"placeholder"},host:{attributes:{rows:"1"},listeners:{input:"_noopInputHandler()"},classAttribute:"cdk-textarea-autosize"},exportAs:["cdkTextareaAutosize"],ngImport:t}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Er,decorators:[{type:d,args:[{selector:"textarea[cdkTextareaAutosize]",exportAs:"cdkTextareaAutosize",host:{class:"cdk-textarea-autosize",rows:"1","(input)":"_noopInputHandler()"}}]}],ctorParameters:function(){return[{type:t.ElementRef},{type:pt.Platform},{type:t.NgZone},{type:void 0,decorators:[{type:p},{type:m,args:[j]}]}]},propDecorators:{minRows:[{type:i,args:["cdkAutosizeMinRows"]}],maxRows:[{type:i,args:["cdkAutosizeMaxRows"]}],enabled:[{type:i,args:["cdkTextareaAutosize"]}],placeholder:[{type:i}]}}); + /** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + class Gr{}Gr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Gr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,declarations:[Mr,Er],exports:[Mr,Er]}),Gr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.0-rc.0",ngImport:t,type:Gr,decorators:[{type:c,args:[{declarations:[Mr,Er],exports:[Mr,Er]}]}]});class Dr extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",description:"tb.mail-body-type.plain-text-description",value:"false"},{name:"tb.mail-body-type.html",description:"tb.mail-body-type.html-text-description",value:"true"},{name:"tb.mail-body-type.use-body-type-template",description:"tb.mail-body-type.dynamic-text-description",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[O.required]],toTemplate:[e?e.toTemplate:null,[O.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[O.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null,[O.required]],bodyTemplate:[e?e.bodyTemplate:null,[O.required]]})}prepareInputConfig(e){return{fromTemplate:ie(e?.fromTemplate)?e.fromTemplate:null,toTemplate:ie(e?.toTemplate)?e.toTemplate:null,ccTemplate:ie(e?.ccTemplate)?e.ccTemplate:null,bccTemplate:ie(e?.bccTemplate)?e.bccTemplate:null,subjectTemplate:ie(e?.subjectTemplate)?e.subjectTemplate:null,mailBodyType:ie(e?.mailBodyType)?e.mailBodyType:null,isHtmlTemplate:ie(e?.isHtmlTemplate)?e.isHtmlTemplate:null,bodyTemplate:ie(e?.bodyTemplate)?e.bodyTemplate:null}}updateValidators(e){"dynamic"===this.toEmailConfigForm.get("mailBodyType").value?this.toEmailConfigForm.get("isHtmlTemplate").enable({emitEvent:!1}):this.toEmailConfigForm.get("isHtmlTemplate").disable({emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["mailBodyType"]}getBodyTypeName(){return this.mailBodyTypes.find((e=>e.value===this.toEmailConfigForm.get("mailBodyType").value)).name}}e("ToEmailConfigComponent",Dr),Dr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Dr,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    tb.rulenode.email-sender
    \n
    \n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
    \n
    \n
    \n
    \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
    \n
    \n
    \n
    \n
    \n
    tb.rulenode.recipients
    \n \n \n
    \n
    \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
    \n
    \n
    \n
    tb.rulenode.message-subject-and-content
    \n \n \n
    \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
    \n \n {{ type.description | translate }}\n \n
    \n
    \n
    \n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:$e.HelpPopupComponent,selector:"[tb-help-popup], [tb-help-popup-content]",inputs:["tb-help-popup","tb-help-popup-content","trigger-text","trigger-style","tb-help-popup-placement","tb-help-popup-style","hintMode"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Er,selector:"textarea[cdkTextareaAutosize]",inputs:["cdkAutosizeMinRows","cdkAutosizeMaxRows","cdkTextareaAutosize","placeholder"],exportAs:["cdkTextareaAutosize"]},{kind:"component",type:X.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex","hideSingleSelectionIndicator"],exportAs:["matSelect"]},{kind:"directive",type:X.MatSelectTrigger,selector:"mat-select-trigger"},{kind:"component",type:ee.MatOption,selector:"mat-option",exportAs:["matOption"]},{kind:"directive",type:qe.MatListItemTitle,selector:"[matListItemTitle]"},{kind:"directive",type:qe.MatListItemMeta,selector:"[matListItemMeta]"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Dr,decorators:[{type:n,args:[{selector:"tb-transformation-node-to-email-config",template:'
    \n
    \n
    tb.rulenode.email-sender
    \n
    \n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.email-from-template-hint\' | translate }}\n \n \n
    \n
    \n
    \n
    \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n
    \n
    \n
    \n
    \n
    \n
    tb.rulenode.recipients
    \n \n \n
    \n
    \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n tb.rulenode.cc-template\n \n \n \n tb.rulenode.bcc-template\n \n \n
    \n
    \n
    \n
    tb.rulenode.message-subject-and-content
    \n \n \n
    \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n tb.rulenode.mail-body-type\n \n \n \n {{ getBodyTypeName() | translate }}\n \n \n \n \n {{ type.name | translate }}\n \n
    \n \n {{ type.description | translate }}\n \n
    \n
    \n
    \n \n tb.rulenode.body-type-template\n \n tb.mail-body-type.after-template-evaluation-hint\n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n
    \n
    \n
    \n',styles:[":host .input-bottom-double-hint{display:inline-flex}:host .input-bottom-double-hint .see-example{flex-shrink:0;padding-right:16px}:host textarea.tb-enable-vertical-resize{resize:vertical}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class wr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.copyFrom=[],this.translation=tn;for(const e of this.translation.keys())this.copyFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.copyKeysConfigForm=this.fb.group({copyFrom:[e.copyFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}configForm(){return this.copyKeysConfigForm}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.copyFrom?en.METADATA:en.DATA:ie(e?.copyFrom)?e.copyFrom:en.DATA,{keys:ie(e?.keys)?e.keys:null,copyFrom:t}}}e("CopyKeysConfigComponent",wr),wr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),wr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:wr,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:wr,decorators:[{type:n,args:[{selector:"tb-transformation-node-copy-keys-config",template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Vr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.renameIn=[],this.translation=rn;for(const e of this.translation.keys())this.renameIn.push({value:e,name:this.translate.instant(this.translation.get(e))})}configForm(){return this.renameKeysConfigForm}onConfigurationSet(e){this.renameKeysConfigForm=this.fb.group({renameIn:[e?e.renameIn:null,[O.required]],renameKeysMapping:[e?e.renameKeysMapping:null,[O.required]]})}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ie(e?.renameIn)?e?.renameIn:en.DATA,{renameKeysMapping:ie(e?.renameKeysMapping)?e.renameKeysMapping:null,renameIn:t}}}e("RenameKeysConfigComponent",Vr),Vr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Vr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Vr,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n
    tb.rulenode.rename-keys-in
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:An,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","labelText","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","popupHelpLink","required"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Vr,decorators:[{type:n,args:[{selector:"tb-transformation-node-rename-keys-config",template:'
    \n
    tb.rulenode.rename-keys-in
    \n
    \n
    \n \n \n {{ data.name }}\n \n \n
    \n
    \n \n \n
    \n',styles:[":host .fetch-to-data-toggle{max-width:420px;width:100%}:host .fx-centered{display:flex;width:100%;justify-content:space-around}\n"]}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Pr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.jsonPathConfigForm}onConfigurationSet(e){this.jsonPathConfigForm=this.fb.group({jsonPath:[e?e.jsonPath:null,[O.required]]})}}e("NodeJsonPathConfigComponent",Pr),Pr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Pr,selector:"tb-transformation-node-json-path-config",usesInheritance:!0,ngImport:t,template:"
    \n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
    \n",dependencies:[{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatHint,selector:"mat-hint",inputs:["align","id"]},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Pr,decorators:[{type:n,args:[{selector:"tb-transformation-node-json-path-config",template:"
    \n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
    \n"}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class Rr extends f{constructor(e,t,n){super(e),this.store=e,this.fb=t,this.translate=n,this.deleteFrom=[],this.translation=nn;for(const e of this.translation.keys())this.deleteFrom.push({value:e,name:this.translate.instant(this.translation.get(e))})}onConfigurationSet(e){this.deleteKeysConfigForm=this.fb.group({deleteFrom:[e.deleteFrom,[O.required]],keys:[e?e.keys:null,[O.required]]})}prepareInputConfig(e){let t;return t=ie(e?.fromMetadata)?e.fromMetadata?en.METADATA:en.DATA:ie(e?.deleteFrom)?e?.deleteFrom:en.DATA,{keys:ie(e?.keys)?e.keys:null,deleteFrom:t}}configForm(){return this.deleteKeysConfigForm}}e("DeleteKeysConfigComponent",Rr),Rr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,deps:[{token:P.Store},{token:R.FormBuilder},{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Rr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Rr,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"component",type:et.StringItemsListComponent,selector:"tb-string-items-list",inputs:["required","disabled","label","placeholder","hint","requiredText","floatLabel","appearance","editable","subscriptSizing","predefinedValues"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"component",type:Bn,selector:"tb-msg-metadata-chip",inputs:["labelText","translation"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Rr,decorators:[{type:n,args:[{selector:"tb-transformation-node-delete-keys-config",template:'
    \n \n \n \n \n help\n \n \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder},{type:Z.TranslateService}]}});class Or extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.deduplicationStrategie=Gt,this.deduplicationStrategies=Object.keys(this.deduplicationStrategie),this.deduplicationStrategiesTranslations=Dt}configForm(){return this.deduplicationConfigForm}onConfigurationSet(e){this.deduplicationConfigForm=this.fb.group({interval:[ie(e?.interval)?e.interval:null,[O.required,O.min(1)]],strategy:[ie(e?.strategy)?e.strategy:null,[O.required]],outMsgType:[ie(e?.outMsgType)?e.outMsgType:null,[O.required]],maxPendingMsgs:[ie(e?.maxPendingMsgs)?e.maxPendingMsgs:null,[O.required,O.min(1),O.max(1e3)]],maxRetries:[ie(e?.maxRetries)?e.maxRetries:null,[O.required,O.min(0),O.max(100)]]})}prepareInputConfig(e){return e||(e={}),e.outMsgType||(e.outMsgType="POST_TELEMETRY_REQUEST"),super.prepareInputConfig(e)}updateValidators(e){this.deduplicationConfigForm.get("strategy").value===this.deduplicationStrategie.ALL?this.deduplicationConfigForm.get("outMsgType").enable({emitEvent:!1}):this.deduplicationConfigForm.get("outMsgType").disable({emitEvent:!1}),this.deduplicationConfigForm.get("outMsgType").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["strategy"]}}e("DeduplicationConfigComponent",Or),Or.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,deps:[{token:P.Store},{token:R.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Or.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Or,selector:"tb-action-node-msg-deduplication-config",usesInheritance:!0,ngImport:t,template:'
    \n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    tb.rulenode.strategy
    \n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
    \n \n \n
    \n
    \n
    \n \n \n tb.rulenode.advanced-settings\n \n
    \n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n'],dependencies:[{kind:"directive",type:H.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{kind:"directive",type:H.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{kind:"component",type:ue.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{kind:"directive",type:J.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["disabled","id","placeholder","name","required","type","errorStateMatcher","aria-describedby","value","readonly"],exportAs:["matInput"]},{kind:"component",type:Y.MatFormField,selector:"mat-form-field",inputs:["hideRequiredMarker","color","floatLabel","appearance","subscriptSizing","hintLabel"],exportAs:["matFormField"]},{kind:"directive",type:Y.MatLabel,selector:"mat-label"},{kind:"directive",type:Y.MatError,selector:"mat-error, [matError]",inputs:["id"]},{kind:"directive",type:Y.MatSuffix,selector:"[matSuffix], [matIconSuffix], [matTextSuffix]",inputs:["matTextSuffix"]},{kind:"directive",type:ce.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{kind:"component",type:We.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{kind:"component",type:We.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{kind:"directive",type:We.MatExpansionPanelTitle,selector:"mat-panel-title"},{kind:"directive",type:R.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{kind:"directive",type:R.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]},{kind:"directive",type:Z.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{kind:"directive",type:Fe.ToggleOption,selector:"tb-toggle-option",inputs:["value"]},{kind:"component",type:ke.ToggleSelectComponent,selector:"tb-toggle-select",inputs:["disabled","selectMediaBreakpoint","appearance","disablePagination"]},{kind:"component",type:_n,selector:"tb-output-message-type-autocomplete",inputs:["subscriptSizing","disabled","required"]},{kind:"component",type:qn,selector:"tb-example-hint",inputs:["hintText","popupHelpLink","textAlign"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Or,decorators:[{type:n,args:[{selector:"tb-action-node-msg-deduplication-config",template:'
    \n \n {{\'tb.rulenode.interval\' | translate}}\n \n \n {{\'tb.rulenode.interval-required\' | translate}}\n \n \n {{\'tb.rulenode.interval-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    tb.rulenode.strategy
    \n \n \n {{ deduplicationStrategiesTranslations.get(strategy) | translate }}\n \n \n \n \n \n \n \n \n
    \n \n \n
    \n
    \n
    \n \n \n tb.rulenode.advanced-settings\n \n
    \n \n {{\'tb.rulenode.max-pending-msgs\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-required\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-pending-msgs-min-error\' | translate}}\n \n help\n \n \n {{\'tb.rulenode.max-retries\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-required\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-max-error\' | translate}}\n \n \n {{\'tb.rulenode.max-retries-min-error\' | translate}}\n \n help\n \n
    \n
    \n
    \n
    \n
    \n',styles:[':host .margin-8{margin:8px}:host .tb-error{letter-spacing:.25px;color:var(--mdc-theme-error, #f44336)}:host .tb-required:after{content:"*";font-size:16px;color:#000000de}:host .help-icon{color:#000;opacity:.38;padding:unset}:host .help-icon:hover{color:#305680;opacity:unset}.same-width-component-row{display:flex;flex-wrap:nowrap;gap:16px}@media screen and (max-width: 599px){.same-width-component-row{gap:8px}}.same-width-component-row>*{flex:1}\n']}]}],ctorParameters:function(){return[{type:P.Store},{type:R.FormBuilder}]}});class _r{}e("RulenodeCoreConfigTransformModule",_r),_r.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,deps:[],target:t.ɵɵFactoryTarget.NgModule}),_r.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:_r,declarations:[Nr,Sr,Dr,wr,Vr,Pr,Rr,Or],imports:[$,M,jn],exports:[Nr,Sr,Dr,wr,Vr,Pr,Rr,Or]}),_r.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,imports:[$,M,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:_r,decorators:[{type:c,args:[{declarations:[Nr,Sr,Dr,wr,Vr,Pr,Rr,Or],imports:[$,M,jn],exports:[Nr,Sr,Dr,wr,Vr,Pr,Rr,Or]}]}]});class Br extends f{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=F}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[O.required]]})}}e("RuleChainInputComponent",Br),Br.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Br.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Br,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
    \n \n \n
    \n',dependencies:[{kind:"component",type:lt.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","useFullEntityId","appearance","required","disabled"],outputs:["entityChanged"]},{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"directive",type:R.FormControlName,selector:"[formControlName]",inputs:["formControlName","disabled","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Br,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-input-config",template:'
    \n \n \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class Kr extends f{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",Kr),Kr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,deps:[{token:P.Store},{token:R.UntypedFormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kr.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"15.2.10",type:Kr,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
    \n
    \n
    \n',dependencies:[{kind:"directive",type:W.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{kind:"directive",type:R.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{kind:"directive",type:R.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{kind:"pipe",type:Z.TranslatePipe,name:"translate"}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Kr,decorators:[{type:n,args:[{selector:"tb-flow-node-rule-chain-output-config",template:'
    \n
    \n
    \n'}]}],ctorParameters:function(){return[{type:P.Store},{type:R.UntypedFormBuilder}]}});class zr{}e("RuleNodeCoreConfigFlowModule",zr),zr.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,deps:[],target:t.ɵɵFactoryTarget.NgModule}),zr.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:zr,declarations:[Br,Kr],imports:[$,M,jn],exports:[Br,Kr]}),zr.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,imports:[$,M,jn]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:zr,decorators:[{type:c,args:[{declarations:[Br,Kr],imports:[$,M,jn],exports:[Br,Kr]}]}]});class Ur{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{id:"Id","additional-info":"Additional Info","advanced-settings":"Advanced settings","create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","copy-message-type":"Copy message type","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","message-type-value":"Message type value","message-type-value-required":"Message type value is required","message-type-value-max-length":"Message type value should be less than 256","output-message-type":"Output message type","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","interval-start":"Interval start","interval-end":"Interval end","time-unit":"Time unit","fetch-mode":"Fetch mode","order-by-timestamp":"Order by timestamp",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. If you want to fetch a single entry, select fetch mode 'First' or 'Last'.","limit-required":"Limit is required.","limit-range":"Limit should be in a range from 2 to 1000.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Allowing range from 1 to 2147483647.","start-interval-value-required":"Interval start is required.","end-interval-value-required":"Interval end is required.",filter:"Filter",switch:"Switch","math-templatization-tooltip":"This field support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","add-message-type":"Add message type","select-message-types-required":"At least one message type should be selected.","select-message-types":"Select message types","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one.","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","notify-device":"Force notification to the device","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","update-attributes-only-on-value-change":"Save attributes only if the value changes","update-attributes-only-on-value-change-hint":"Updates the attributes on every incoming message disregarding if their value has changed. Increases API usage and reduces performance.","update-attributes-only-on-value-change-hint-enabled":"Updates the attributes only if their value has changed. If the value is not changed, no update to the attribute timestamp nor attribute change notification will be sent.","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-on-update-hint":"If enabled, force notification to the device about shared attributes update. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn off the notification, the message metadata must contain the 'notifyDevice' parameter set to 'false'. Any other case will trigger the notification to the device.","notify-device-on-delete-hint":"If enabled, force notification to the device about shared attributes removal. If disabled, the notification behavior is controlled by the 'notifyDevice' parameter from the incoming message metadata. To turn on the notification, the message metadata must contain the 'notifyDevice' parameter set to 'true'. In any other case, the notification will not be triggered to the device.","latest-timeseries":"Latest time-series data keys","timeseries-keys":"Timeseries keys","timeseries-keys-required":"At least one timeseries key should be selected.","add-timeseries-key":"Add timeseries key","add-message-field":"Add message field","relation-search-parameters":"Relation search parameters","add-metadata-field":"Add metadata field","data-keys":"Message field names","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Use regular expression to copy keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name. Multiple field names supported.",interval:"Interval","interval-required":"Interval is required","interval-hint":"Deduplication interval in seconds.","interval-min-error":"Min allowed value is 1","max-pending-msgs":"Max pending messages","max-pending-msgs-hint":"Maximum number of messages that are stored in memory for each unique deduplication id.","max-pending-msgs-required":"Max pending messages is required","max-pending-msgs-max-error":"Max allowed value is 1000","max-pending-msgs-min-error":"Min allowed value is 1","max-retries":"Max retries","max-retries-required":"Max retries is required","max-retries-hint":"Maximum number of retries to push the deduplicated messages into the queue. 10 seconds delay is used between retries","max-retries-max-error":"Max allowed value is 100","max-retries-min-error":"Min allowed value is 0",strategy:"Strategy","strategy-required":"Strategy is required","strategy-all-hint":"Return all messages that arrived during deduplication period as a single JSON array message. Where each element represents object with msg and metadata inner properties.","strategy-first-hint":"Return first message that arrived during deduplication period.","strategy-last-hint":"Return last message that arrived during deduplication period.",first:"First",last:"Last",all:"All","output-msg-type-hint":"The message type of the deduplication result.","queue-name-hint":"The queue name where the deduplication result will be published.",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",message:"Message",metadata:"Metadata","current-key-name":"Current key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Metadata field names","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","max-relation-level-error":"Value should be greater than 0 or unspecified.","relation-type":"Relation type","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","add-telemetry-key":"Add telemetry key","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern.\n\nTips & tricks:\nPress 'Enter' to complete field name input.\nPress 'Backspace' to delete field name.\nMultiple field names supported.","fetch-into":"Fetch into","attr-mapping":"Attributes mapping:","source-attribute":"Source attribute key","source-attribute-required":"Source attribute key is required.","source-telemetry":"Source telemetry key","source-telemetry-required":"Source telemetry key is required.","target-key":"Target key","target-key-required":"Target key is required.","attr-mapping-required":"At least one mapping entry should be specified.","fields-mapping":"Fields mapping","relations-query-config-direction-suffix":"originator","profile-name":"Profile name","fetch-circle-parameter-info-from-metadata-hint":'Metadata field \'{{perimeterKeyName}}\' should be defined in next format: {"latitude":48.196, "longitude":24.6532, "radius":100.0, "radiusUnit":"METER"}',"fetch-poligon-parameter-info-from-metadata-hint":"Metadata field '{{perimeterKeyName}}' should be defined in next format: [[48.19736,24.65235],[48.19800,24.65060],...,[48.19849,24.65420]]","short-templatization-tooltip":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","fields-mapping-required":"At least one field mapping should be specified.","at-least-one-field-required":"At least one input field must have a value(s) provided.","originator-fields-sv-map-hint":"Target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","sv-map-hint":"Only target key fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","new-originator":"New originator","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related entity","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity by name pattern","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","select-entity-types":"Select entity types","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From","from-template-required":"From is required","message-to-metadata":"Message to metadata","metadata-to-message":"Metadata to message","from-message":"From message","from-metadata":"From metadata","to-template":"To","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc","bcc-template":"Bcc","subject-template":"Subject","subject-template-required":"Subject Template is required","body-template":"Body","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","body-type-template":"Body type template","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","parse-to-plain-text":"Parse to plain text","parse-to-plain-text-hint":'If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = "Hello,\\t\\"world\\"" will be parsed to Hello, "world"',"read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file","private-key":"Client private key file",cert:"Client certificate file","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-dynamic-interval":"Use dynamic interval","metadata-dynamic-interval-hint":"Interval start and end input fields support templatization. Note that the substituted template value should be set in milliseconds. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all specified fields are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-to-specific-entity-tooltip":"If enabled, checks the presence of relation with a specific entity otherwise, checks the presence of relation with any entity. In both cases, relation lookup is based on configured direction and type.","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval":"Interval start","end-interval":"Interval end","start-interval-required":"Interval start is required.","end-interval-required":"Interval end is required.","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'Press "Enter" to complete field input.',"select-details":"Select details","entity-details-id":"Id","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","email-sender":"Email sender","fields-to-check":"Fields to check","add-detail":"Add detail","check-all-keys-tooltip":"If enabled, checks the presence of all fields listed in the message and metadata field names within the incoming message and its metadata.","fields-to-check-hint":'Press "Enter" to complete field name input. Multiple field names supported.',"entity-details-list-empty":"At least one detail should be selected.","alarm-status":"Alarm status","alarm-required":"At least one alarm status should be selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"Enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-field-name":"Latitude field name","longitude-field-name":"Longitude field name","latitude-field-name-required":"Latitude field name is required.","longitude-field-name-required":"Longitude field name is required.","fetch-perimeter-info-from-metadata":"Fetch perimeter information from metadata","fetch-perimeter-info-from-metadata-tooltip":"If perimeter type is set to 'Polygon' the value of metadata field '{{perimeterKeyName}}' will be set as perimeter definition without additional parsing of the value. Otherwise, if perimeter type is set to 'Circle' the value of '{{perimeterKeyName}}' metadata field will be parsed to extract 'latitude', 'longitude', 'radius', 'radiusUnit' fields that uses for circle perimeter definition.","perimeter-key-name":"Perimeter key name","perimeter-key-name-hint":"Metadata field name that includes perimeter information.","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units","range-units-required":"Range units is required.",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch timestamp for the latest telemetry values","get-latest-value-with-ts-hint":'If selected, the latest telemetry values will also include timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.","number-of-digits-after-floating-point":"Number of digits after floating point","number-of-digits-after-floating-point-range":"Number of digits after floating point should be in a range from 0 to 15.","failure-if-delta-negative":"Tell Failure if delta is negative","failure-if-delta-negative-tooltip":"Rule node forces failure of message processing if delta value is negative.","use-caching":"Use caching","use-caching-tooltip":'Rule node will cache the value of "{{inputValueKey}}" that arrives from the incoming message to improve performance. Note that the cache will not be updated if you modify the "{{inputValueKey}}" value elsewhere.',"add-time-difference-between-readings":'Add the time difference between "{{inputValueKey}}" readings',"add-time-difference-between-readings-tooltip":'If enabled, the rule node will add the "{{periodValueKey}}" to the outbound message.',"period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":"Use ${metadataKey} for value from metadata, $[messageKey] for value from message body.","alarm-severity-pattern-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":"All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time-series","message-body-type":"Message","message-metadata-type":"Metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-source-field-input":"Source","argument-source-field-input-required":"Argument source is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","add-entity-type":"Add entity type","add-device-profile":"Add device profile","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Use 0 to convert result to integer","add-to-message-field-input":"Add to message","add-to-metadata-field-input":"Add to metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Specify a mathematical expression to evaluate. Default expression demonstrates how to transform Fahrenheit to Celsius","retained-message":"Retained","attributes-mapping":"Attributes mapping","latest-telemetry-mapping":"Latest telemetry mapping","add-mapped-attribute-to":"Add mapped attributes to","add-mapped-latest-telemetry-to":"Add mapped latest telemetry to","add-mapped-fields-to":"Add mapped fields to","add-selected-details-to":"Add selected details to","clear-selected-types":"Clear selected types","clear-selected-details":"Clear selected details","clear-selected-fields":"Clear selected fields","clear-selected-keys":"Clear selected keys","geofence-configuration":"Geofence configuration","coordinate-field-names":"Coordinate field names","coordinate-field-hint":"Rule node tries to retrieve the specified fields from the message. If they are not present, it will look them up in the metadata.","presence-monitoring-strategy":"Presence monitoring strategy","presence-monitoring-strategy-on-first-message":"On first message","presence-monitoring-strategy-on-each-message":"On each message","presence-monitoring-strategy-on-first-message-hint":"Reports presence status 'Inside' or 'Outside' on the first message after the configured minimal duration has passed since previous presence status 'Entered' or 'Left' update.","presence-monitoring-strategy-on-each-message-hint":"Reports presence status 'Inside' or 'Outside' on each message after presence status 'Entered' or 'Left' update.","fetch-credentials-to":"Fetch credentials to","add-originator-attributes-to":"Add originator attributes to","originator-attributes":"Originator attributes","fetch-latest-telemetry-with-timestamp":"Fetch latest telemetry with timestamp","fetch-latest-telemetry-with-timestamp-tooltip":'If selected, latest telemetry values will be added to the outbound metadata with timestamp, e.g: "{{latestTsKeyName}}": "{"ts":1574329385897, "value":42}"',"tell-failure":"Tell failure if any of the attributes are missing","tell-failure-tooltip":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"created-time":"Created time","chip-help":"Press 'Enter' to complete {{inputName}} input. \nPress 'Backspace' to delete {{inputName}}. \nMultiple values supported.",detail:"detail","field-name":"field name","device-profile":"device profile","entity-type":"entity type","message-type":"message type","timeseries-key":"timeseries key",type:"Type","first-name":"First name","last-name":"Last name",label:"Label","originator-fields-mapping":"Originator fields mapping","add-mapped-originator-fields-to":"Add mapped originator fields to",fields:"Fields","skip-empty-fields":"Skip empty fields","skip-empty-fields-tooltip":"Fields with empty values will not be added to the output message/output metadata.","fetch-interval":"Fetch interval","fetch-strategy":"Fetch strategy","fetch-timeseries-from-to":"Fetch timeseries from {{startInterval}} {{startIntervalTimeUnit}} ago to {{endInterval}} {{endIntervalTimeUnit}} ago.","fetch-timeseries-from-to-invalid":'Fetch timeseries invalid ("Interval start" should be less than "Interval end").',"use-metadata-dynamic-interval-tooltip":"If selected, the rule node will use dynamic interval start and end based on the message and metadata patterns.","all-mode-hint":'If selected fetch mode "All" rule node will retrieve telemetry from the fetch interval with configurable query parameters.',"first-mode-hint":'If selected fetch mode "First" rule node will retrieve the closest telemetry to the fetch interval\'s start.',"last-mode-hint":'If selected fetch mode "Last" rule node will retrieve the closest telemetry to the fetch interval\'s end.',ascending:"Ascending",descending:"Descending",min:"Min",max:"Max",average:"Average",sum:"Sum",count:"Count",none:"None","last-level-relation-tooltip":"If selected, the rule node will search related entities only on the level set in the max relation level.","last-level-device-relation-tooltip":"If selected, the rule node will search related devices only on the level set in the max relation level.","data-to-fetch":"Data to fetch","mapping-of-customers":"Mapping of customer's","map-fields-required":"All mapping fields are required.",attributes:"Attributes","related-device-attributes":"Related device attributes","add-selected-attributes-to":"Add selected attributes to","device-profiles":"Device profiles","mapping-of-tenant":"Mapping of tenant's","add-attribute-key":"Add attribute key","message-template":"Message template","message-template-required":"Message template is required","use-system-slack-settings":"Use system slack settings","slack-api-token":"Slack API token","slack-api-token-required":"Slack API token is required","keys-mapping":"keys mapping","add-key":"Add key",recipients:"Recipients","message-subject-and-content":"Message subject and content","template-rules-hint":"Both input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","originator-customer-desc":"Use customer of incoming message originator as new originator.","originator-tenant-desc":"Use current tenant as new originator.","originator-related-entity-desc":"Use related entity as new originator. Lookup based on configured relation type and direction.","originator-alarm-originator-desc":"Use alarm originator as new originator. Only if incoming message originator is alarm entity.","originator-entity-by-name-pattern-desc":"Use entity fetched from DB as new originator. Lookup based on entity type and specified name pattern.","email-from-template-hint":"Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata.","recipients-block-main-hint":"Comma-separated address list. All input fields support templatization. Use $[messageKey] to extract value from the message and ${metadataKey} to extract value from the metadata."},"key-val":{key:"Key",value:"Value","see-examples":"See examples.","remove-entry":"Remove entry","remove-mapping-entry":"Remove mapping entry","add-mapping-entry":"Add mapping","add-entry":"Add entry","copy-key-values-from":"Copy key-values from","delete-key-values":"Delete key-values","delete-key-values-from":"Delete key-values from","at-least-one-key-error":"At least one key should be selected.","unique-key-value-pair-error":"'{{keyText}}' must be different from the '{{valText}}'!"},"mail-body-type":{"plain-text":"Plain text",html:"HTML",dynamic:"Dynamic","use-body-type-template":"Use body type template","plain-text-description":"Simple, unformatted text with no special styling or formating.","html-text-description":"Allows you to use HTML tags for formatting, links and images in your mai body.","dynamic-text-description":"Allows to use Plain Text or HTML body type dynamically based on templatization feature.","after-template-evaluation-hint":"After template evaluation value should be true for HTML, and false for Plain text."}}},!0)}(e)}}e("RuleNodeCoreConfigModule",Ur),Ur.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,deps:[{token:Z.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),Ur.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"14.0.0",version:"15.2.10",ngImport:t,type:Ur,declarations:[dt],imports:[$,M],exports:[$n,Ir,or,xr,_r,zr,dt]}),Ur.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,imports:[$,M,$n,Ir,or,xr,_r,zr]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"15.2.10",ngImport:t,type:Ur,decorators:[{type:c,args:[{declarations:[dt],imports:[$,M],exports:[$n,Ir,or,xr,_r,zr,dt]}]}],ctorParameters:function(){return[{type:Z.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map From e55d14325a7658193eca00115c7f918098a8406c Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 17:09:32 +0200 Subject: [PATCH 132/137] Refactor rate limiting to be configurable and use existing class --- .../server/actors/ActorSystemContext.java | 4 + .../actors/ruleChain/DefaultTbContext.java | 5 + .../src/main/resources/thingsboard.yml | 9 ++ .../rule/engine/api/TbContext.java | 2 + .../rule/engine/action/TbDeviceStateNode.java | 91 +++++--------- .../engine/action/TbDeviceStateNodeTest.java | 114 +++++------------- 6 files changed, 80 insertions(+), 145 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 6082c3f9f0..2a4f132be8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -561,6 +561,10 @@ public class ActorSystemContext { @Getter private boolean externalNodeForceAck; + @Value("${state.rule.node.deviceState.rateLimit:1:1,30:60,60:3600}") + @Getter + private String deviceStateNodeRateLimitConfig; + @Getter @Setter private TbActorSystem actorSystem; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index b97201005c..9110ab794d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -689,6 +689,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getDeviceStateManager(); } + @Override + public String getDeviceStateNodeRateLimitConfig() { + return mainCtx.getDeviceStateNodeRateLimitConfig(); + } + @Override public TbClusterService getClusterService() { return mainCtx.getClusterService(); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 82a347d41c..5b22a3b7be 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -794,6 +794,15 @@ state: # Used only when state.persistToTelemetry is set to 'true' and Cassandra is used for timeseries data. # 0 means time-to-live mechanism is disabled. telemetryTtl: "${STATE_TELEMETRY_TTL:0}" + # Configuration properties for rule nodes related to device activity state + rule: + node: + # Device state rule node + deviceState: + # Defines the rate at which device connectivity events can be triggered. + # Comma-separated list of capacity:duration pairs that define bandwidth capacity and refill duration for token bucket rate limit algorithm. + # Refill is set to be greedy. Please refer to Bucket4j library documentation for more details. + rateLimit: "${DEVICE_STATE_NODE_RATE_LIMIT_CONFIGURATION:1:1,30:60,60:3600}" # Tbel parameters tbel: diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 32fb81ccd9..fc2980650b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -282,6 +282,8 @@ public interface TbContext { RuleEngineDeviceStateManager getDeviceStateManager(); + String getDeviceStateNodeRateLimitConfig(); + TbClusterService getClusterService(); DashboardService getDashboardService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java index b0f426d905..7925178b33 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeviceStateNode.java @@ -15,8 +15,8 @@ */ package org.thingsboard.rule.engine.action; -import com.google.common.base.Stopwatch; import lombok.extern.slf4j.Slf4j; +import org.springframework.util.ConcurrentReferenceHashMap; import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -28,17 +28,15 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.tools.TbRateLimits; -import java.time.Duration; import java.util.EnumSet; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; @Slf4j @RuleNode( @@ -47,8 +45,8 @@ import java.util.concurrent.ConcurrentMap; nodeDescription = "Triggers device connectivity events", nodeDetails = "If incoming message originator is a device, registers configured event for that device in the Device State Service, which sends appropriate message to the Rule Engine." + " If metadata ts property is present, it will be used as event timestamp. Otherwise, the message timestamp will be used." + - " Incoming message is forwarded using the Success chain, unless an unexpected error occurs during message processing" + - " then incoming message is forwarded using the Failure chain." + + " If originator entity type is not DEVICE or unexpected error happened during processing, then incoming message is forwarded using Failure chain." + + " If rate of connectivity events for a given originator is too high, then incoming message is forwarded using Rate limited chain. " + "
    " + "Supported device connectivity events are:" + "
      " + @@ -59,6 +57,7 @@ import java.util.concurrent.ConcurrentMap; "
    " + "This node is particularly useful when device isn't using transports to receive data, such as when fetching data from external API or computing new data within the rule chain.", configClazz = TbDeviceStateNodeConfiguration.class, + relationTypes = {TbNodeConnectionType.SUCCESS, TbNodeConnectionType.FAILURE, "Rate limited"}, uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeDeviceStateConfig" ) @@ -67,12 +66,9 @@ public class TbDeviceStateNode implements TbNode { private static final Set SUPPORTED_EVENTS = EnumSet.of( TbMsgType.CONNECT_EVENT, TbMsgType.ACTIVITY_EVENT, TbMsgType.DISCONNECT_EVENT, TbMsgType.INACTIVITY_EVENT ); - private static final Duration ONE_SECOND = Duration.ofSeconds(1L); - private static final Duration ENTRY_EXPIRATION_TIME = Duration.ofDays(1L); - private static final Duration ENTRY_CLEANUP_PERIOD = Duration.ofHours(1L); - - private Stopwatch stopwatch; - private ConcurrentMap lastActivityEventTimestamps; + private static final String DEFAULT_RATE_LIMIT_CONFIG = "1:1,30:60,60:3600"; + private ConcurrentReferenceHashMap rateLimits; + private String rateLimitConfig; private TbMsgType event; @Override @@ -85,19 +81,19 @@ public class TbDeviceStateNode implements TbNode { throw new TbNodeException("Unsupported event: " + event, true); } this.event = event; - lastActivityEventTimestamps = new ConcurrentHashMap<>(); - stopwatch = Stopwatch.createStarted(); - scheduleCleanupMsg(ctx); + rateLimits = new ConcurrentReferenceHashMap<>(); + String deviceStateNodeRateLimitConfig = ctx.getDeviceStateNodeRateLimitConfig(); + try { + rateLimitConfig = new TbRateLimits(deviceStateNodeRateLimitConfig).getConfiguration(); + } catch (Exception e) { + log.error("[{}][{}] Invalid rate limit configuration provided: [{}]. Will use default value [{}].", + ctx.getTenantId().getId(), ctx.getSelfId().getId(), deviceStateNodeRateLimitConfig, DEFAULT_RATE_LIMIT_CONFIG, e); + rateLimitConfig = DEFAULT_RATE_LIMIT_CONFIG; + } } @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (msg.isTypeOf(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG)) { - removeStaleEntries(); - scheduleCleanupMsg(ctx); - return; - } - EntityType originatorEntityType = msg.getOriginator().getEntityType(); if (!EntityType.DEVICE.equals(originatorEntityType)) { ctx.tellFailure(msg, new IllegalArgumentException( @@ -105,41 +101,18 @@ public class TbDeviceStateNode implements TbNode { )); return; } - DeviceId originator = new DeviceId(msg.getOriginator().getId()); - - lastActivityEventTimestamps.compute(originator, (__, lastEventTs) -> { - Duration now = stopwatch.elapsed(); - - if (lastEventTs == null) { - sendEventAndTell(ctx, originator, msg); - return now; + rateLimits.compute(originator, (__, rateLimit) -> { + if (rateLimit == null) { + rateLimit = new TbRateLimits(rateLimitConfig); } - - Duration elapsedSinceLastEventSent = now.minus(lastEventTs); - if (elapsedSinceLastEventSent.compareTo(ONE_SECOND) < 0) { - ctx.tellSuccess(msg); - return lastEventTs; + boolean isNotRateLimited = rateLimit.tryConsume(); + if (isNotRateLimited) { + sendEventAndTell(ctx, originator, msg); + } else { + ctx.tellNext(msg, "Rate limited"); } - - sendEventAndTell(ctx, originator, msg); - return now; - }); - } - - private void scheduleCleanupMsg(TbContext ctx) { - TbMsg cleanupMsg = ctx.newMsg( - null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, ctx.getSelfId(), TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING - ); - ctx.tellSelf(cleanupMsg, ENTRY_CLEANUP_PERIOD.toMillis()); - } - - private void removeStaleEntries() { - lastActivityEventTimestamps.entrySet().removeIf(entry -> { - Duration now = stopwatch.elapsed(); - Duration lastEventTs = entry.getValue(); - Duration elapsedSinceLastEventSent = now.minus(lastEventTs); - return elapsedSinceLastEventSent.compareTo(ENTRY_EXPIRATION_TIME) > 0; + return rateLimit; }); } @@ -184,16 +157,16 @@ public class TbDeviceStateNode implements TbNode { @Override public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { - lastActivityEventTimestamps.entrySet().removeIf(entry -> !ctx.isLocalEntity(entry.getKey())); + rateLimits.entrySet().removeIf(entry -> !ctx.isLocalEntity(entry.getKey())); } @Override public void destroy() { - if (lastActivityEventTimestamps != null) { - lastActivityEventTimestamps.clear(); - lastActivityEventTimestamps = null; + if (rateLimits != null) { + rateLimits.clear(); + rateLimits = null; } - stopwatch = null; + rateLimitConfig = null; event = null; } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java index 19cb3c55bd..9352d64809 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbDeviceStateNodeTest.java @@ -15,8 +15,6 @@ */ package org.thingsboard.rule.engine.action; -import com.google.common.base.Stopwatch; -import com.google.common.base.Ticker; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,6 +27,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.ConcurrentReferenceHashMap; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineDeviceStateManager; import org.thingsboard.rule.engine.api.TbContext; @@ -45,11 +44,9 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.tools.TbRateLimits; -import java.time.Duration; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -57,9 +54,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; @ExtendWith(MockitoExtension.class) public class TbDeviceStateNodeTest { @@ -71,21 +69,12 @@ public class TbDeviceStateNodeTest { @Captor private static ArgumentCaptor callbackCaptor; private TbDeviceStateNode node; - private RuleNodeId nodeId; private TbDeviceStateNodeConfiguration config; private static final TenantId TENANT_ID = TenantId.fromUUID(UUID.randomUUID()); private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID()); private static final long METADATA_TS = 123L; - private TbMsg cleanupMsg; private TbMsg msg; - private long nowNanos; - private final Ticker controlledTicker = new Ticker() { - @Override - public long read() { - return nowNanos; - } - }; @BeforeEach public void setup() { @@ -96,8 +85,6 @@ public class TbDeviceStateNodeTest { var data = JacksonUtil.newObjectNode(); data.put("humidity", 58.3); msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metaData, JacksonUtil.toString(data)); - nodeId = new RuleNodeId(UUID.randomUUID()); - cleanupMsg = TbMsg.newMsg(null, TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, nodeId, TbMsgMetaData.EMPTY, TbMsg.EMPTY_STRING); } @BeforeEach @@ -121,80 +108,42 @@ public class TbDeviceStateNodeTest { } @Test - public void givenValidConfig_whenInit_thenSchedulesCleanupMsg() { + public void givenInvalidRateLimitConfig_whenInit_thenUsesDefaultConfig() { // GIVEN - given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); + given(ctxMock.getDeviceStateNodeRateLimitConfig()).willReturn("invalid rate limit config"); + given(ctxMock.getTenantId()).willReturn(TENANT_ID); + given(ctxMock.getSelfId()).willReturn(new RuleNodeId(UUID.randomUUID())); // WHEN try { initNode(TbMsgType.ACTIVITY_EVENT); } catch (Exception e) { - fail("Node failed to initialize."); + fail("Node failed to initialize!", e); } // THEN - verifyCleanupMsgSent(); - } - - @Test - public void givenCleanupMsg_whenOnMsg_thenCleansStaleEntries() { - // GIVEN - given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); - - ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); - - Stopwatch stopwatch = Stopwatch.createStarted(controlledTicker); - ReflectionTestUtils.setField(node, "stopwatch", stopwatch); - - // WHEN - Duration expirationTime = Duration.ofDays(1L); - - DeviceId staleId = DEVICE_ID; - Duration staleTs = Duration.ofHours(4L); - lastActivityEventTimestamps.put(staleId, staleTs); - - DeviceId goodId = new DeviceId(UUID.randomUUID()); - Duration goodTs = staleTs.plus(expirationTime); - lastActivityEventTimestamps.put(goodId, goodTs); - - nowNanos = staleTs.toNanos() + expirationTime.toNanos() + 1; - node.onMsg(ctxMock, cleanupMsg); - - // THEN - assertThat(lastActivityEventTimestamps) - .containsKey(goodId) - .doesNotContainKey(staleId) - .size().isOne(); - - verifyCleanupMsgSent(); - then(ctxMock).shouldHaveNoMoreInteractions(); + String actualRateLimitConfig = (String) ReflectionTestUtils.getField(node, "rateLimitConfig"); + assertThat(actualRateLimitConfig).isEqualTo("1:1,30:60,60:3600"); } @Test public void givenMsgArrivedTooFast_whenOnMsg_thenRateLimitsThisMsg() { // GIVEN - ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); + ConcurrentReferenceHashMap rateLimits = new ConcurrentReferenceHashMap<>(); + ReflectionTestUtils.setField(node, "rateLimits", rateLimits); - Stopwatch stopwatch = Stopwatch.createStarted(controlledTicker); - ReflectionTestUtils.setField(node, "stopwatch", stopwatch); + var rateLimitMock = mock(TbRateLimits.class); + rateLimits.put(DEVICE_ID, rateLimitMock); - // WHEN - Duration firstEventTs = Duration.ofMillis(1000L); - lastActivityEventTimestamps.put(DEVICE_ID, firstEventTs); + given(rateLimitMock.tryConsume()).willReturn(false); - Duration tooFastEventTs = firstEventTs.plus(Duration.ofMillis(999L)); - nowNanos = tooFastEventTs.toNanos(); + // WHEN node.onMsg(ctxMock, msg); // THEN - Duration actualEventTs = lastActivityEventTimestamps.get(DEVICE_ID); - assertThat(actualEventTs).isEqualTo(firstEventTs); - - then(ctxMock).should().tellSuccess(msg); + then(ctxMock).should().tellNext(msg, "Rate limited"); + then(ctxMock).should(never()).tellSuccess(any()); + then(ctxMock).should(never()).tellFailure(any(), any()); then(ctxMock).shouldHaveNoMoreInteractions(); then(deviceStateManagerMock).shouldHaveNoInteractions(); } @@ -202,25 +151,25 @@ public class TbDeviceStateNodeTest { @Test public void givenHasNonLocalDevices_whenOnPartitionChange_thenRemovesEntriesForNonLocalDevices() { // GIVEN - ConcurrentMap lastActivityEventTimestamps = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(node, "lastActivityEventTimestamps", lastActivityEventTimestamps); + ConcurrentReferenceHashMap rateLimits = new ConcurrentReferenceHashMap<>(); + ReflectionTestUtils.setField(node, "rateLimits", rateLimits); - lastActivityEventTimestamps.put(DEVICE_ID, Duration.ofHours(24L)); + rateLimits.put(DEVICE_ID, new TbRateLimits("1:1")); given(ctxMock.isLocalEntity(eq(DEVICE_ID))).willReturn(true); DeviceId nonLocalDeviceId1 = new DeviceId(UUID.randomUUID()); - lastActivityEventTimestamps.put(nonLocalDeviceId1, Duration.ofHours(30L)); + rateLimits.put(nonLocalDeviceId1, new TbRateLimits("2:2")); given(ctxMock.isLocalEntity(eq(nonLocalDeviceId1))).willReturn(false); DeviceId nonLocalDeviceId2 = new DeviceId(UUID.randomUUID()); - lastActivityEventTimestamps.put(nonLocalDeviceId2, Duration.ofHours(32L)); + rateLimits.put(nonLocalDeviceId2, new TbRateLimits("3:3")); given(ctxMock.isLocalEntity(eq(nonLocalDeviceId2))).willReturn(false); // WHEN node.onPartitionChangeMsg(ctxMock, new PartitionChangeMsg(ServiceType.TB_RULE_ENGINE)); // THEN - assertThat(lastActivityEventTimestamps) + assertThat(rateLimits) .containsKey(DEVICE_ID) .doesNotContainKey(nonLocalDeviceId1) .doesNotContainKey(nonLocalDeviceId2) @@ -275,6 +224,7 @@ public class TbDeviceStateNodeTest { @Test public void givenMetadataDoesNotContainTs_whenOnMsg_thenMsgTsIsUsedAsEventTs() { // GIVEN + given(ctxMock.getDeviceStateNodeRateLimitConfig()).willReturn("1:1"); try { initNode(TbMsgType.ACTIVITY_EVENT); } catch (TbNodeException e) { @@ -298,9 +248,8 @@ public class TbDeviceStateNodeTest { @MethodSource public void givenSupportedEventAndDeviceOriginator_whenOnMsg_thenCorrectEventIsSentWithCorrectCallback(TbMsgType supportedEventType, Runnable actionVerification) { // GIVEN - given(ctxMock.getSelfId()).willReturn(nodeId); - given(ctxMock.newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING))).willReturn(cleanupMsg); given(ctxMock.getTenantId()).willReturn(TENANT_ID); + given(ctxMock.getDeviceStateNodeRateLimitConfig()).willReturn("1:1"); given(ctxMock.getDeviceStateManager()).willReturn(deviceStateManagerMock); try { @@ -308,7 +257,6 @@ public class TbDeviceStateNodeTest { } catch (TbNodeException e) { fail("Node failed to initialize!", e); } - verifyCleanupMsgSent(); // WHEN node.onMsg(ctxMock, msg); @@ -345,10 +293,4 @@ public class TbDeviceStateNodeTest { node.init(ctxMock, nodeConfig); } - private void verifyCleanupMsgSent() { - then(ctxMock).should().getSelfId(); - then(ctxMock).should().newMsg(isNull(), eq(TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG), eq(nodeId), eq(TbMsgMetaData.EMPTY), eq(TbMsg.EMPTY_STRING)); - then(ctxMock).should().tellSelf(eq(cleanupMsg), eq(Duration.ofHours(1L).toMillis())); - } - } From 06bd88f7e9a4188459b5d1476685eb54c6280c27 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 17:28:07 +0200 Subject: [PATCH 133/137] Remove device state entries cleanup message --- .../java/org/thingsboard/server/common/data/msg/TbMsgType.java | 1 - .../org/thingsboard/server/common/data/msg/TbMsgTypeTest.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java index b2014f4bb0..763f245a09 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/msg/TbMsgType.java @@ -76,7 +76,6 @@ public enum TbMsgType { DEDUPLICATION_TIMEOUT_SELF_MSG(null, true), DELAY_TIMEOUT_SELF_MSG(null, true), MSG_COUNT_SELF_MSG(null, true), - DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG(null, true), // Custom or N/A type: NA; diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java index 529f6e6646..2221b236bd 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/msg/TbMsgTypeTest.java @@ -26,7 +26,6 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.DEDUPLICATION_TIM import static org.thingsboard.server.common.data.msg.TbMsgType.DELAY_TIMEOUT_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG; -import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.DEVICE_UPDATE_SELF_MSG; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_ASSIGNED_TO_EDGE; import static org.thingsboard.server.common.data.msg.TbMsgType.ENTITY_UNASSIGNED_FROM_EDGE; @@ -54,7 +53,6 @@ class TbMsgTypeTest { DEDUPLICATION_TIMEOUT_SELF_MSG, DELAY_TIMEOUT_SELF_MSG, MSG_COUNT_SELF_MSG, - DEVICE_STATE_STALE_ENTRIES_CLEANUP_SELF_MSG, NA ); From 89db4b1188477e2b61d645efaf61341bcfecefe0 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 17:37:28 +0200 Subject: [PATCH 134/137] Change proto numbers to avoid conflicts with PE --- common/proto/src/main/proto/queue.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 8c1918d5ef..149ac9c81f 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -1295,9 +1295,9 @@ message ToCoreMsg { LifecycleEventProto lifecycleEventMsg = 8; ErrorEventProto errorEventMsg = 9; ToDeviceActorNotificationMsgProto toDeviceActorNotification = 10; - DeviceConnectProto deviceConnectMsg = 11; - DeviceDisconnectProto deviceDisconnectMsg = 12; - DeviceInactivityProto deviceInactivityMsg = 13; + DeviceConnectProto deviceConnectMsg = 13; + DeviceDisconnectProto deviceDisconnectMsg = 14; + DeviceInactivityProto deviceInactivityMsg = 15; } /* High priority messages with low latency are handled by ThingsBoard Core Service separately */ From e586f367faa38044c4f6dee2958fb8bb022cf6e1 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 18:05:01 +0200 Subject: [PATCH 135/137] Change log level to avoid cluttering logs --- .../service/state/DefaultRuleEngineDeviceStateManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java index 087f470fed..722fcd1aac 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java @@ -175,7 +175,7 @@ public class DefaultRuleEngineDeviceStateManager implements RuleEngineDeviceStat TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); if (serviceInfoProvider.isService(ServiceType.TB_CORE) && tpi.isMyPartition() && deviceStateService.isPresent()) { - log.info("[{}][{}] Forwarding device connectivity event to local service. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); + log.debug("[{}][{}] Forwarding device connectivity event to local service. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); try { eventInfo.forwardToLocalService(); } catch (Exception e) { @@ -186,7 +186,7 @@ public class DefaultRuleEngineDeviceStateManager implements RuleEngineDeviceStat callback.onSuccess(); } else { TransportProtos.ToCoreMsg msg = eventInfo.toQueueMsg(); - log.info("[{}][{}] Sending device connectivity message to core. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); + log.debug("[{}][{}] Sending device connectivity message to core. Event time: [{}].", tenantId.getId(), deviceId.getId(), eventTime); clusterService.pushMsgToCore(tpi, UUID.randomUUID(), msg, new SimpleTbQueueCallback(__ -> callback.onSuccess(), callback::onFailure)); } } From 11cf5e7de899ca4ab44ce6633dceb04c07241255 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 18:36:16 +0200 Subject: [PATCH 136/137] Change proto numbering --- common/proto/src/main/proto/queue.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 149ac9c81f..e432b0dcec 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -1295,9 +1295,9 @@ message ToCoreMsg { LifecycleEventProto lifecycleEventMsg = 8; ErrorEventProto errorEventMsg = 9; ToDeviceActorNotificationMsgProto toDeviceActorNotification = 10; - DeviceConnectProto deviceConnectMsg = 13; - DeviceDisconnectProto deviceDisconnectMsg = 14; - DeviceInactivityProto deviceInactivityMsg = 15; + DeviceConnectProto deviceConnectMsg = 50; + DeviceDisconnectProto deviceDisconnectMsg = 51; + DeviceInactivityProto deviceInactivityMsg = 52; } /* High priority messages with low latency are handled by ThingsBoard Core Service separately */ From 73afd9a7f4e6190f1cc430a36af3c369e15dd8a3 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 15 Feb 2024 18:55:12 +0200 Subject: [PATCH 137/137] Make device state manage a rule engine component --- .../java/org/thingsboard/server/actors/ActorSystemContext.java | 2 +- .../service/state/DefaultRuleEngineDeviceStateManager.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 2a4f132be8..e3ebbdf892 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -204,7 +204,7 @@ public class ActorSystemContext { @Getter private DeviceCredentialsService deviceCredentialsService; - @Autowired + @Autowired(required = false) @Getter private RuleEngineDeviceStateManager deviceStateManager; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java index 722fcd1aac..af2b03ec8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultRuleEngineDeviceStateManager.java @@ -29,12 +29,14 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.SimpleTbQueueCallback; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.util.TbRuleEngineComponent; import java.util.Optional; import java.util.UUID; @Slf4j @Service +@TbRuleEngineComponent public class DefaultRuleEngineDeviceStateManager implements RuleEngineDeviceStateManager { private final TbServiceInfoProvider serviceInfoProvider;